diff options
| author | jvoisin | 2019-04-28 19:25:45 +0200 |
|---|---|---|
| committer | jvoisin | 2019-05-01 17:55:35 +0200 |
| commit | 95169906931e60ace651e8f0aefe0fbe375b2f40 (patch) | |
| tree | 0a9ed871dca3104f769419bbeff91b9c2d9c56dd /libmat2/archive.py | |
| parent | a7ebb587e19ce1177a7ef067e2da74e4964ff19e (diff) | |
Add some verification for "dangerous" tarfiles
Diffstat (limited to 'libmat2/archive.py')
| -rw-r--r-- | libmat2/archive.py | 53 |
1 files changed, 52 insertions, 1 deletions
diff --git a/libmat2/archive.py b/libmat2/archive.py index 969bbd8..e6c11c1 100644 --- a/libmat2/archive.py +++ b/libmat2/archive.py | |||
| @@ -15,7 +15,7 @@ from . import abstract, UnknownMemberPolicy, parser_factory | |||
| 15 | assert Set | 15 | assert Set |
| 16 | assert Pattern | 16 | assert Pattern |
| 17 | 17 | ||
| 18 | # pylint: disable=not-callable,assignment-from-no-return | 18 | # pylint: disable=not-callable,assignment-from-no-return,too-many-branches |
| 19 | 19 | ||
| 20 | # An ArchiveClass is a class representing an archive, | 20 | # An ArchiveClass is a class representing an archive, |
| 21 | # while an ArchiveMember is a class representing an element | 21 | # while an ArchiveMember is a class representing an element |
| @@ -238,6 +238,57 @@ class TarParser(ArchiveBasedAbstractParser): | |||
| 238 | def is_archive_valid(self): | 238 | def is_archive_valid(self): |
| 239 | if tarfile.is_tarfile(self.filename) is False: | 239 | if tarfile.is_tarfile(self.filename) is False: |
| 240 | raise ValueError | 240 | raise ValueError |
| 241 | self.__check_tarfile_safety() | ||
| 242 | |||
| 243 | def __check_tarfile_safety(self): | ||
| 244 | """Checks if the tarfile doesn't have any "suspicious" members. | ||
| 245 | |||
| 246 | This is a rewrite of this patch: https://bugs.python.org/file47826/safetarfile-4.diff | ||
| 247 | inspired by this bug from 2014: https://bugs.python.org/issue21109 | ||
| 248 | because Python's stdlib doesn't provide a way to "safely" extract | ||
| 249 | things from a tar file. | ||
| 250 | """ | ||
| 251 | names = set() | ||
| 252 | with tarfile.open(self.filename) as f: | ||
| 253 | members = f.getmembers() | ||
| 254 | for member in members: | ||
| 255 | name = member.name | ||
| 256 | if os.path.isabs(name): | ||
| 257 | raise ValueError("The archive %s contains a file with an " \ | ||
| 258 | "absolute path: %s" % (self.filename, name)) | ||
| 259 | elif os.path.normpath(name).startswith('../') or '/../' in name: | ||
| 260 | raise ValueError("The archive %s contains a file with an " \ | ||
| 261 | "path traversal attack: %s" % (self.filename, name)) | ||
| 262 | |||
| 263 | if name in names: | ||
| 264 | raise ValueError("The archive %s contains two times the same " \ | ||
| 265 | "file: %s" % (self.filename, name)) | ||
| 266 | else: | ||
| 267 | names.add(name) | ||
| 268 | |||
| 269 | if member.isfile(): | ||
| 270 | if member.mode & stat.S_ISUID: | ||
| 271 | raise ValueError("The archive %s contains a setuid file: %s" % \ | ||
| 272 | (self.filename, name)) | ||
| 273 | elif member.mode & stat.S_ISGID: | ||
| 274 | raise ValueError("The archive %s contains a setgid file: %s" % \ | ||
| 275 | (self.filename, name)) | ||
| 276 | elif member.issym(): | ||
| 277 | linkname = member.linkname | ||
| 278 | if os.path.normpath(linkname).startswith('..'): | ||
| 279 | raise ValueError('The archive %s contains a symlink pointing' \ | ||
| 280 | 'outside of the archive via a path traversal: %s -> %s' % \ | ||
| 281 | (self.filename, name, linkname)) | ||
| 282 | if os.path.isabs(linkname): | ||
| 283 | raise ValueError('The archive %s contains a symlink pointing' \ | ||
| 284 | 'outside of the archive: %s -> %s' % \ | ||
| 285 | (self.filename, name, linkname)) | ||
| 286 | elif member.isdev(): | ||
| 287 | raise ValueError("The archive %s contains a non-regular " \ | ||
| 288 | "file: %s" % (self.filename, name)) | ||
| 289 | elif member.islnk(): | ||
| 290 | raise ValueError("The archive %s contains a hardlink: %s" \ | ||
| 291 | % (self.filename, name)) | ||
| 241 | 292 | ||
| 242 | @staticmethod | 293 | @staticmethod |
| 243 | def _clean_member(member: ArchiveMember) -> ArchiveMember: | 294 | def _clean_member(member: ArchiveMember) -> ArchiveMember: |
