summaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
authorMike Frysinger <vapier@google.com>2019-08-02 15:57:57 -0400
committerMike Frysinger <vapier@google.com>2020-02-04 20:34:23 +0000
commite6a202f790daaf204513b8c53b824fcc246f9972 (patch)
tree6907f26e5a17a7b39f62e401b895088f1c178540 /tests
parent04122b7261319dae3abcaf0eb63af7ed937dc463 (diff)
downloadgit-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')
-rw-r--r--tests/test_project.py204
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
25import tempfile 25import tempfile
26import unittest 26import unittest
27 27
28import error
28import git_config 29import git_config
29import project 30import 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
140class 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
191class 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
291class 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))