diff options
author | Mike Frysinger <vapier@google.com> | 2019-08-02 15:57:57 -0400 |
---|---|---|
committer | Mike Frysinger <vapier@google.com> | 2020-02-04 20:34:23 +0000 |
commit | e6a202f790daaf204513b8c53b824fcc246f9972 (patch) | |
tree | 6907f26e5a17a7b39f62e401b895088f1c178540 /tests/test_project.py | |
parent | 04122b7261319dae3abcaf0eb63af7ed937dc463 (diff) | |
download | git-repo-e6a202f790daaf204513b8c53b824fcc246f9972.tar.gz |
project: add basic path checks for <copyfile> & <linkfile>
Reject paths in <copyfile> & <linkfile> that try to use symlinks or
non-file or non-dirs.
We don't fully validate <linkfile> when src is a glob as it's a bit
complicated -- any component in the src could be the glob. We make
sure the destination is a directory, and that any paths in that dir
are created as symlinks. So while this can be used to read any path,
it can't be abused to write to any paths.
Bug: https://crbug.com/gerrit/11218
Change-Id: I68b6d789b5ca4e43f569e75e8b293b3e13d3224b
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/233074
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
Reviewed-by: Michael Mortensen <mmortensen@google.com>
Diffstat (limited to 'tests/test_project.py')
-rw-r--r-- | tests/test_project.py | 204 |
1 files changed, 204 insertions, 0 deletions
diff --git a/tests/test_project.py b/tests/test_project.py index 77126dff..6d82da11 100644 --- a/tests/test_project.py +++ b/tests/test_project.py | |||
@@ -25,6 +25,7 @@ import subprocess | |||
25 | import tempfile | 25 | import tempfile |
26 | import unittest | 26 | import unittest |
27 | 27 | ||
28 | import error | ||
28 | import git_config | 29 | import git_config |
29 | import project | 30 | import project |
30 | 31 | ||
@@ -134,3 +135,206 @@ class ReviewableBranchTests(unittest.TestCase): | |||
134 | self.assertFalse(rb.base_exists) | 135 | self.assertFalse(rb.base_exists) |
135 | # Hard to assert anything useful about this. | 136 | # Hard to assert anything useful about this. |
136 | self.assertTrue(rb.date) | 137 | self.assertTrue(rb.date) |
138 | |||
139 | |||
140 | class CopyLinkTestCase(unittest.TestCase): | ||
141 | """TestCase for stub repo client checkouts. | ||
142 | |||
143 | It'll have a layout like: | ||
144 | tempdir/ # self.tempdir | ||
145 | checkout/ # self.topdir | ||
146 | git-project/ # self.worktree | ||
147 | |||
148 | Attributes: | ||
149 | tempdir: A dedicated temporary directory. | ||
150 | worktree: The top of the repo client checkout. | ||
151 | topdir: The top of a project checkout. | ||
152 | """ | ||
153 | |||
154 | def setUp(self): | ||
155 | self.tempdir = tempfile.mkdtemp(prefix='repo_tests') | ||
156 | self.topdir = os.path.join(self.tempdir, 'checkout') | ||
157 | self.worktree = os.path.join(self.topdir, 'git-project') | ||
158 | os.makedirs(self.topdir) | ||
159 | os.makedirs(self.worktree) | ||
160 | |||
161 | def tearDown(self): | ||
162 | shutil.rmtree(self.tempdir, ignore_errors=True) | ||
163 | |||
164 | @staticmethod | ||
165 | def touch(path): | ||
166 | with open(path, 'w') as f: | ||
167 | pass | ||
168 | |||
169 | def assertExists(self, path, msg=None): | ||
170 | """Make sure |path| exists.""" | ||
171 | if os.path.exists(path): | ||
172 | return | ||
173 | |||
174 | if msg is None: | ||
175 | msg = ['path is missing: %s' % path] | ||
176 | while path != '/': | ||
177 | path = os.path.dirname(path) | ||
178 | if not path: | ||
179 | # If we're given something like "foo", abort once we get to "". | ||
180 | break | ||
181 | result = os.path.exists(path) | ||
182 | msg.append('\tos.path.exists(%s): %s' % (path, result)) | ||
183 | if result: | ||
184 | msg.append('\tcontents: %r' % os.listdir(path)) | ||
185 | break | ||
186 | msg = '\n'.join(msg) | ||
187 | |||
188 | raise self.failureException(msg) | ||
189 | |||
190 | |||
191 | class CopyFile(CopyLinkTestCase): | ||
192 | """Check _CopyFile handling.""" | ||
193 | |||
194 | def CopyFile(self, src, dest): | ||
195 | return project._CopyFile(self.worktree, src, self.topdir, dest) | ||
196 | |||
197 | def test_basic(self): | ||
198 | """Basic test of copying a file from a project to the toplevel.""" | ||
199 | src = os.path.join(self.worktree, 'foo.txt') | ||
200 | self.touch(src) | ||
201 | cf = self.CopyFile('foo.txt', 'foo') | ||
202 | cf._Copy() | ||
203 | self.assertExists(os.path.join(self.topdir, 'foo')) | ||
204 | |||
205 | def test_src_subdir(self): | ||
206 | """Copy a file from a subdir of a project.""" | ||
207 | src = os.path.join(self.worktree, 'bar', 'foo.txt') | ||
208 | os.makedirs(os.path.dirname(src)) | ||
209 | self.touch(src) | ||
210 | cf = self.CopyFile('bar/foo.txt', 'new.txt') | ||
211 | cf._Copy() | ||
212 | self.assertExists(os.path.join(self.topdir, 'new.txt')) | ||
213 | |||
214 | def test_dest_subdir(self): | ||
215 | """Copy a file to a subdir of a checkout.""" | ||
216 | src = os.path.join(self.worktree, 'foo.txt') | ||
217 | self.touch(src) | ||
218 | cf = self.CopyFile('foo.txt', 'sub/dir/new.txt') | ||
219 | self.assertFalse(os.path.exists(os.path.join(self.topdir, 'sub'))) | ||
220 | cf._Copy() | ||
221 | self.assertExists(os.path.join(self.topdir, 'sub', 'dir', 'new.txt')) | ||
222 | |||
223 | def test_update(self): | ||
224 | """Make sure changed files get copied again.""" | ||
225 | src = os.path.join(self.worktree, 'foo.txt') | ||
226 | dest = os.path.join(self.topdir, 'bar') | ||
227 | with open(src, 'w') as f: | ||
228 | f.write('1st') | ||
229 | cf = self.CopyFile('foo.txt', 'bar') | ||
230 | cf._Copy() | ||
231 | self.assertExists(dest) | ||
232 | with open(dest) as f: | ||
233 | self.assertEqual(f.read(), '1st') | ||
234 | |||
235 | with open(src, 'w') as f: | ||
236 | f.write('2nd!') | ||
237 | cf._Copy() | ||
238 | with open(dest) as f: | ||
239 | self.assertEqual(f.read(), '2nd!') | ||
240 | |||
241 | def test_src_block_symlink(self): | ||
242 | """Do not allow reading from a symlinked path.""" | ||
243 | src = os.path.join(self.worktree, 'foo.txt') | ||
244 | sym = os.path.join(self.worktree, 'sym') | ||
245 | self.touch(src) | ||
246 | os.symlink('foo.txt', sym) | ||
247 | self.assertExists(sym) | ||
248 | cf = self.CopyFile('sym', 'foo') | ||
249 | self.assertRaises(error.ManifestInvalidPathError, cf._Copy) | ||
250 | |||
251 | def test_src_block_symlink_traversal(self): | ||
252 | """Do not allow reading through a symlink dir.""" | ||
253 | src = os.path.join(self.worktree, 'bar', 'passwd') | ||
254 | os.symlink('/etc', os.path.join(self.worktree, 'bar')) | ||
255 | self.assertExists(src) | ||
256 | cf = self.CopyFile('bar/foo.txt', 'foo') | ||
257 | self.assertRaises(error.ManifestInvalidPathError, cf._Copy) | ||
258 | |||
259 | def test_src_block_dir(self): | ||
260 | """Do not allow copying from a directory.""" | ||
261 | src = os.path.join(self.worktree, 'dir') | ||
262 | os.makedirs(src) | ||
263 | cf = self.CopyFile('dir', 'foo') | ||
264 | self.assertRaises(error.ManifestInvalidPathError, cf._Copy) | ||
265 | |||
266 | def test_dest_block_symlink(self): | ||
267 | """Do not allow writing to a symlink.""" | ||
268 | src = os.path.join(self.worktree, 'foo.txt') | ||
269 | self.touch(src) | ||
270 | os.symlink('dest', os.path.join(self.topdir, 'sym')) | ||
271 | cf = self.CopyFile('foo.txt', 'sym') | ||
272 | self.assertRaises(error.ManifestInvalidPathError, cf._Copy) | ||
273 | |||
274 | def test_dest_block_symlink_traversal(self): | ||
275 | """Do not allow writing through a symlink dir.""" | ||
276 | src = os.path.join(self.worktree, 'foo.txt') | ||
277 | self.touch(src) | ||
278 | os.symlink('/tmp', os.path.join(self.topdir, 'sym')) | ||
279 | cf = self.CopyFile('foo.txt', 'sym/foo.txt') | ||
280 | self.assertRaises(error.ManifestInvalidPathError, cf._Copy) | ||
281 | |||
282 | def test_src_block_dir(self): | ||
283 | """Do not allow copying to a directory.""" | ||
284 | src = os.path.join(self.worktree, 'foo.txt') | ||
285 | self.touch(src) | ||
286 | os.makedirs(os.path.join(self.topdir, 'dir')) | ||
287 | cf = self.CopyFile('foo.txt', 'dir') | ||
288 | self.assertRaises(error.ManifestInvalidPathError, cf._Copy) | ||
289 | |||
290 | |||
291 | class LinkFile(CopyLinkTestCase): | ||
292 | """Check _LinkFile handling.""" | ||
293 | |||
294 | def LinkFile(self, src, dest): | ||
295 | return project._LinkFile(self.worktree, src, self.topdir, dest) | ||
296 | |||
297 | def test_basic(self): | ||
298 | """Basic test of linking a file from a project into the toplevel.""" | ||
299 | src = os.path.join(self.worktree, 'foo.txt') | ||
300 | self.touch(src) | ||
301 | lf = self.LinkFile('foo.txt', 'foo') | ||
302 | lf._Link() | ||
303 | dest = os.path.join(self.topdir, 'foo') | ||
304 | self.assertExists(dest) | ||
305 | self.assertTrue(os.path.islink(dest)) | ||
306 | self.assertEqual('git-project/foo.txt', os.readlink(dest)) | ||
307 | |||
308 | def test_src_subdir(self): | ||
309 | """Link to a file in a subdir of a project.""" | ||
310 | src = os.path.join(self.worktree, 'bar', 'foo.txt') | ||
311 | os.makedirs(os.path.dirname(src)) | ||
312 | self.touch(src) | ||
313 | lf = self.LinkFile('bar/foo.txt', 'foo') | ||
314 | lf._Link() | ||
315 | self.assertExists(os.path.join(self.topdir, 'foo')) | ||
316 | |||
317 | def test_dest_subdir(self): | ||
318 | """Link a file to a subdir of a checkout.""" | ||
319 | src = os.path.join(self.worktree, 'foo.txt') | ||
320 | self.touch(src) | ||
321 | lf = self.LinkFile('foo.txt', 'sub/dir/foo/bar') | ||
322 | self.assertFalse(os.path.exists(os.path.join(self.topdir, 'sub'))) | ||
323 | lf._Link() | ||
324 | self.assertExists(os.path.join(self.topdir, 'sub', 'dir', 'foo', 'bar')) | ||
325 | |||
326 | def test_update(self): | ||
327 | """Make sure changed targets get updated.""" | ||
328 | dest = os.path.join(self.topdir, 'sym') | ||
329 | |||
330 | src = os.path.join(self.worktree, 'foo.txt') | ||
331 | self.touch(src) | ||
332 | lf = self.LinkFile('foo.txt', 'sym') | ||
333 | lf._Link() | ||
334 | self.assertEqual('git-project/foo.txt', os.readlink(dest)) | ||
335 | |||
336 | # Point the symlink somewhere else. | ||
337 | os.unlink(dest) | ||
338 | os.symlink('/', dest) | ||
339 | lf._Link() | ||
340 | self.assertEqual('git-project/foo.txt', os.readlink(dest)) | ||