Skip to content

Commit 957a4f7

Browse files
Merge branch 'develop'
2 parents 4aad0e0 + 6f29728 commit 957a4f7

File tree

16 files changed

+62
-42
lines changed

16 files changed

+62
-42
lines changed

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,9 @@ typings/
163163
*.tgz
164164

165165
# Except test file
166-
!tests/functional/workflows/ruby_bundler/test_data/test.tgz
166+
!tests/functional/testdata/test.tgz
167+
!tests/functional/testdata/path_reversal_uxix.tgz
168+
!tests/functional/testdata/path_reversal_win.tgz
167169

168170
# Yarn Integrity file
169171
.yarn-integrity

CODEOWNERS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners
2+
3+
* @aws/serverless-application-experience-sbt

aws_lambda_builders/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@
44

55
# Changing version will trigger a new release!
66
# Please make the version change as the last step of your development.
7-
__version__ = "1.23.0"
7+
__version__ = "1.23.1"
88
RPC_PROTOCOL_VERSION = "0.3"

aws_lambda_builders/utils.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import os
88
import logging
99
from pathlib import Path
10+
from typing import Union
1011

1112
from aws_lambda_builders.architecture import X86_64, ARM64
1213

@@ -196,3 +197,28 @@ def create_symlink_or_copy(source: str, destination: str) -> None:
196197
exc_info=ex if LOG.isEnabledFor(logging.DEBUG) else None,
197198
)
198199
copytree(source, destination)
200+
201+
202+
def _is_within_directory(directory: Union[str, os.PathLike], target: Union[str, os.PathLike]) -> bool:
203+
"""Checks if target is located under directory"""
204+
abs_directory = os.path.abspath(directory)
205+
abs_target = os.path.abspath(target)
206+
207+
prefix = os.path.commonprefix([abs_directory, abs_target])
208+
209+
return prefix == abs_directory
210+
211+
212+
def extract_tarfile(tarfile_path: Union[str, os.PathLike], unpack_dir: Union[str, os.PathLike]) -> None:
213+
"""Extracts a tarfile"""
214+
import tarfile
215+
216+
with tarfile.open(tarfile_path, "r:*") as tar:
217+
# Makes sure the tar file is sanitized and is free of directory traversal vulnerability
218+
# See: https://github.com/advisories/GHSA-gw9q-c7gh-j9vm
219+
for member in tar.getmembers():
220+
member_path = os.path.join(unpack_dir, member.name)
221+
if not _is_within_directory(unpack_dir, member_path):
222+
raise tarfile.ExtractError("Attempted Path Traversal in Tar File")
223+
224+
tar.extractall(unpack_dir)

aws_lambda_builders/workflows/nodejs_npm/actions.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import logging
66

77
from aws_lambda_builders.actions import BaseAction, Purpose, ActionFailedError
8+
from aws_lambda_builders.utils import extract_tarfile
89
from .npm import NpmExecutionError
910

1011
LOG = logging.getLogger(__name__)
@@ -64,7 +65,7 @@ def execute(self):
6465

6566
LOG.debug("NODEJS extracting to %s", self.artifacts_dir)
6667

67-
self.osutils.extract_tarfile(tarfile_path, self.artifacts_dir)
68+
extract_tarfile(tarfile_path, self.artifacts_dir)
6869

6970
except NpmExecutionError as ex:
7071
raise ActionFailedError(str(ex))

aws_lambda_builders/workflows/nodejs_npm/utils.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,6 @@ class OSUtils(object):
2020
def copy_file(self, file_path, destination_path):
2121
return shutil.copy2(file_path, destination_path)
2222

23-
def extract_tarfile(self, tarfile_path, unpack_dir):
24-
with tarfile.open(tarfile_path, "r:*") as tar:
25-
tar.extractall(unpack_dir)
26-
2723
def file_exists(self, filename):
2824
return os.path.isfile(filename)
2925

aws_lambda_builders/workflows/python_pip/packager.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from email.parser import FeedParser
1010

1111
from aws_lambda_builders.architecture import ARM64, X86_64
12+
from aws_lambda_builders.utils import extract_tarfile
1213
from .compat import pip_import_string
1314
from .compat import pip_no_compile_c_env_vars
1415
from .compat import pip_no_compile_c_shim
@@ -619,7 +620,7 @@ def _unpack_sdist_into_dir(self, sdist_path, unpack_dir):
619620
if sdist_path.endswith(".zip"):
620621
self._osutils.extract_zipfile(sdist_path, unpack_dir)
621622
elif sdist_path.endswith((".tar.gz", ".tar.bz2")):
622-
self._osutils.extract_tarfile(sdist_path, unpack_dir)
623+
extract_tarfile(sdist_path, unpack_dir)
623624
else:
624625
raise InvalidSourceDistributionNameError(sdist_path)
625626
# There should only be one directory unpacked.

aws_lambda_builders/workflows/python_pip/utils.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,6 @@ def extract_zipfile(self, zipfile_path, unpack_dir):
5656
with zipfile.ZipFile(zipfile_path, "r") as z:
5757
z.extractall(unpack_dir)
5858

59-
def extract_tarfile(self, tarfile_path, unpack_dir):
60-
with tarfile.open(tarfile_path, "r:*") as tar:
61-
tar.extractall(unpack_dir)
62-
6359
def directory_exists(self, path):
6460
return os.path.isdir(path)
6561

aws_lambda_builders/workflows/ruby_bundler/utils.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,6 @@ class OSUtils(object):
1616
unit test actions in memory
1717
"""
1818

19-
def extract_tarfile(self, tarfile_path, unpack_dir):
20-
with tarfile.open(tarfile_path, "r:*") as tar:
21-
tar.extractall(unpack_dir)
22-
2319
def popen(self, command, stdout=None, stderr=None, env=None, cwd=None):
2420
p = subprocess.Popen(command, stdout=stdout, stderr=stderr, env=env, cwd=cwd)
2521
return p

tests/functional/test_utils.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import os
22
import tempfile
33
import shutil
4+
import platform
5+
from tarfile import ExtractError
46

57
from unittest import TestCase
68

7-
from aws_lambda_builders.utils import copytree, get_goarch
9+
from aws_lambda_builders.utils import copytree, get_goarch, extract_tarfile
810

911

1012
class TestCopyTree(TestCase):
@@ -63,6 +65,24 @@ def test_must_return_valid_go_architecture(self):
6365
self.assertEqual(get_goarch(""), "amd64")
6466

6567

68+
class TestExtractTarFile(TestCase):
69+
def test_extract_tarfile_unpacks_a_tar(self):
70+
test_tar = os.path.join(os.path.dirname(__file__), "testdata", "test.tgz")
71+
test_dir = tempfile.mkdtemp()
72+
extract_tarfile(test_tar, test_dir)
73+
output_files = set(os.listdir(test_dir))
74+
shutil.rmtree(test_dir)
75+
self.assertEqual({"test_utils.py"}, output_files)
76+
77+
def test_raise_exception_for_unsafe_tarfile(self):
78+
tar_filename = "path_reversal_win.tgz" if platform.system().lower() == "windows" else "path_reversal_uxix.tgz"
79+
test_tar = os.path.join(os.path.dirname(__file__), "testdata", tar_filename)
80+
test_dir = tempfile.mkdtemp()
81+
self.assertRaisesRegexp(
82+
ExtractError, "Attempted Path Traversal in Tar File", extract_tarfile, test_tar, test_dir
83+
)
84+
85+
6686
def file(*args):
6787
path = os.path.join(*args)
6888
basedir = os.path.dirname(path)

0 commit comments

Comments
 (0)