Merge lp:~lool/snapcraft/no-separate-toplevel-dir into lp:~snappy-dev/snapcraft/core

Proposed by Loïc Minier
Status: Merged
Approved by: Michael Terry
Approved revision: 107
Merged at revision: 109
Proposed branch: lp:~lool/snapcraft/no-separate-toplevel-dir
Merge into: lp:~snappy-dev/snapcraft/core
Diff against target: 116 lines (+53/-12)
3 files modified
integration-tests/data/simple-tar/snapcraft.yaml (+12/-0)
integration-tests/units/jobs.pxu (+6/-0)
snapcraft/__init__.py (+35/-12)
To merge this branch: bzr merge lp:~lool/snapcraft/no-separate-toplevel-dir
Reviewer Review Type Date Requested Status
Michael Terry (community) Approve
Review via email: mp+265754@code.launchpad.net

Commit message

Rework tarball unpack to use tarfile. New implementation deals properly with tarballs which ship all files under a common directory, but without an entry for this common directory. Also handles dangerous tarballs with pathes starting with / or ../. Adjust tests accordingly.

NB: this was found while trying to use the upstream tomcat tarball; toplevel parent dir didn't get stripped on unpack.

Description of the change

Rework tarball unpack to use tarfile. New implementation deals properly with tarballs which ship all files under a common directory, but without an entry for this common directory. Also handles dangerous tarballs with pathes starting with / or ../. Adjust tests accordingly.

NB: this was found while trying to use the upstream tomcat tarball; toplevel parent dir didn't get stripped on unpack.

To post a comment you must log in.
Revision history for this message
Michael Terry (mterry) wrote :

Thanks! I didn't realize tar didn't handle evil paths for us. :(

And I totally forgot about the tarfile module.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'integration-tests/data/simple-tar/evil-parent.tar.gz'
2Binary files integration-tests/data/simple-tar/evil-parent.tar.gz 1970-01-01 00:00:00 +0000 and integration-tests/data/simple-tar/evil-parent.tar.gz 2015-07-24 01:25:43 +0000 differ
3=== added file 'integration-tests/data/simple-tar/evil-slash.tar.gz'
4Binary files integration-tests/data/simple-tar/evil-slash.tar.gz 1970-01-01 00:00:00 +0000 and integration-tests/data/simple-tar/evil-slash.tar.gz 2015-07-24 01:25:43 +0000 differ
5=== added file 'integration-tests/data/simple-tar/nodir.tar.xz'
6Binary files integration-tests/data/simple-tar/nodir.tar.xz 1970-01-01 00:00:00 +0000 and integration-tests/data/simple-tar/nodir.tar.xz 2015-07-24 01:25:43 +0000 differ
7=== added file 'integration-tests/data/simple-tar/simple.tar.bz2'
8Binary files integration-tests/data/simple-tar/simple.tar.bz2 1970-01-01 00:00:00 +0000 and integration-tests/data/simple-tar/simple.tar.bz2 2015-07-24 01:25:43 +0000 differ
9=== modified file 'integration-tests/data/simple-tar/snapcraft.yaml'
10--- integration-tests/data/simple-tar/snapcraft.yaml 2015-07-17 19:39:00 +0000
11+++ integration-tests/data/simple-tar/snapcraft.yaml 2015-07-24 01:25:43 +0000
12@@ -5,9 +5,21 @@
13 onedeep:
14 plugin: tar-content
15 source: onedeep.tar.gz
16+ nodir:
17+ plugin: tar-content
18+ source: nodir.tar.xz
19 flat:
20 plugin: tar-content
21 source: flat.tar.xz
22+ simple:
23+ plugin: tar-content
24+ source: simple.tar.bz2
25+ evil-parent:
26+ plugin: tar-content
27+ source: evil-parent.tar.gz
28+ evil-slash:
29+ plugin: tar-content
30+ source: evil-slash.tar.gz
31 project:
32 plugin: make-project
33 source: project.tar.xz
34
35=== modified file 'integration-tests/units/jobs.pxu'
36--- integration-tests/units/jobs.pxu 2015-07-22 21:05:20 +0000
37+++ integration-tests/units/jobs.pxu 2015-07-24 01:25:43 +0000
38@@ -82,6 +82,12 @@
39 test -e stage/onedeep
40 test -e stage/onedeepdir/onedeep2
41 test -e stage/oneflat
42+ test -e stage/dir-simple
43+ test -e stage/top-simple
44+ test -e stage/notop
45+ test -e stage/notopdir
46+ test -e stage/parent
47+ test -e stage/slash
48 test "$(./stage/bin/test)" = "tarproject"
49
50 id: snapcraft/normal/simple-make
51
52=== modified file 'snapcraft/__init__.py'
53--- snapcraft/__init__.py 2015-07-22 18:59:28 +0000
54+++ snapcraft/__init__.py 2015-07-24 01:25:43 +0000
55@@ -15,9 +15,11 @@
56 # along with this program. If not, see <http://www.gnu.org/licenses/>.
57
58 import os
59+import re
60+import tarfile
61+import urllib.parse
62+
63 import snapcraft.common
64-import subprocess
65-import urllib.parse
66
67
68 class BasePlugin:
69@@ -102,16 +104,37 @@
70 else:
71 tarball = os.path.abspath(source)
72
73- # If there's a single toplevel directory, ignore it
74- try:
75- topfiles = subprocess.check_output(['tar', '--list', '-f', tarball, '--exclude', '*/*']).strip()
76- except Exception:
77- return False
78- strip_cmd = []
79- if topfiles and len(topfiles.split(b'\n')) == 1 and chr(topfiles[-1]) == '/':
80- strip_cmd = ['--strip-components=1']
81-
82- return self.run(['tar'] + strip_cmd + ['-xf', tarball], cwd=destdir)
83+ with tarfile.open(tarball) as tar:
84+ def filter_members(tar):
85+ """Filters members and member names:
86+ - strips common prefix
87+ - bans dangerous names"""
88+ members = tar.getmembers()
89+ common = os.path.commonprefix([m.name for m in members])
90+
91+ # commonprefix() works a character at a time and will
92+ # consider "d/ab" and "d/abc" to have common prefix "d/ab";
93+ # check all members either start with common dir
94+ for m in members:
95+ if not (m.name.startswith(common + "/") or
96+ m.isdir() and m.name == common):
97+ # commonprefix() didn't return a dir name; go up one
98+ # level
99+ common = os.path.dirname(common)
100+ break
101+
102+ for m in members:
103+ if m.name == common:
104+ continue
105+ if m.name.startswith(common + "/"):
106+ m.name = m.name[len(common + "/"):]
107+ # strip leading "/", "./" or "../" as many times as needed
108+ m.name = re.sub(r'^(\.{0,2}/)*', r'', m.name)
109+ yield m
110+
111+ tar.extractall(members=filter_members(tar), path=destdir)
112+
113+ return True
114
115 def get_source(self, source, source_type=None, source_tag=None, source_branch=None):
116 if source_type is None:

Subscribers

People subscribed via source and target branches