diff options
author | Mike Frysinger <vapier@google.com> | 2021-03-01 21:38:08 -0500 |
---|---|---|
committer | Mike Frysinger <vapier@google.com> | 2021-03-02 03:18:57 +0000 |
commit | 541339720451b0a05dc7ebe83e17bafb89863c6f (patch) | |
tree | 769d23948beab6523127880f013fc806de8eef87 | |
parent | 13cb7f799dc61093ed69726093a5af8bf48c65d1 (diff) | |
download | git-repo-541339720451b0a05dc7ebe83e17bafb89863c6f.tar.gz |
manifest: relax include name rules for user-specified pathv2.13.1
Allow the user to specify relative or absolute or any other funky
path that they want when using `repo init` or `repo sync`. Our
goal is to restrict the paths in the remote manifest git repo we
cloned from the network, not protect the user from themselves.
Bug: https://crbug.com/gerrit/14156
Change-Id: I1ccfb2a6bd1dce2bd765e261bef0bbf0f8a9beb6
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/298823
Reviewed-by: Jonathan Nieder <jrn@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
-rw-r--r-- | error.py | 4 | ||||
-rw-r--r-- | manifest_xml.py | 41 | ||||
-rw-r--r-- | tests/test_manifest_xml.py | 32 |
3 files changed, 62 insertions, 15 deletions
@@ -22,12 +22,12 @@ class ManifestParseError(Exception): | |||
22 | """ | 22 | """ |
23 | 23 | ||
24 | 24 | ||
25 | class ManifestInvalidRevisionError(Exception): | 25 | class ManifestInvalidRevisionError(ManifestParseError): |
26 | """The revision value in a project is incorrect. | 26 | """The revision value in a project is incorrect. |
27 | """ | 27 | """ |
28 | 28 | ||
29 | 29 | ||
30 | class ManifestInvalidPathError(Exception): | 30 | class ManifestInvalidPathError(ManifestParseError): |
31 | """A path used in <copyfile> or <linkfile> is incorrect. | 31 | """A path used in <copyfile> or <linkfile> is incorrect. |
32 | """ | 32 | """ |
33 | 33 | ||
diff --git a/manifest_xml.py b/manifest_xml.py index cd5954df..e96e0620 100644 --- a/manifest_xml.py +++ b/manifest_xml.py | |||
@@ -624,16 +624,22 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md | |||
624 | b = b[len(R_HEADS):] | 624 | b = b[len(R_HEADS):] |
625 | self.branch = b | 625 | self.branch = b |
626 | 626 | ||
627 | # The manifestFile was specified by the user which is why we allow include | ||
628 | # paths to point anywhere. | ||
627 | nodes = [] | 629 | nodes = [] |
628 | nodes.append(self._ParseManifestXml(self.manifestFile, | 630 | nodes.append(self._ParseManifestXml( |
629 | self.manifestProject.worktree)) | 631 | self.manifestFile, self.manifestProject.worktree, |
632 | restrict_includes=False)) | ||
630 | 633 | ||
631 | if self._load_local_manifests and self.local_manifests: | 634 | if self._load_local_manifests and self.local_manifests: |
632 | try: | 635 | try: |
633 | for local_file in sorted(platform_utils.listdir(self.local_manifests)): | 636 | for local_file in sorted(platform_utils.listdir(self.local_manifests)): |
634 | if local_file.endswith('.xml'): | 637 | if local_file.endswith('.xml'): |
635 | local = os.path.join(self.local_manifests, local_file) | 638 | local = os.path.join(self.local_manifests, local_file) |
636 | nodes.append(self._ParseManifestXml(local, self.repodir)) | 639 | # Since local manifests are entirely managed by the user, allow |
640 | # them to point anywhere the user wants. | ||
641 | nodes.append(self._ParseManifestXml( | ||
642 | local, self.repodir, restrict_includes=False)) | ||
637 | except OSError: | 643 | except OSError: |
638 | pass | 644 | pass |
639 | 645 | ||
@@ -651,7 +657,19 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md | |||
651 | 657 | ||
652 | self._loaded = True | 658 | self._loaded = True |
653 | 659 | ||
654 | def _ParseManifestXml(self, path, include_root, parent_groups=''): | 660 | def _ParseManifestXml(self, path, include_root, parent_groups='', |
661 | restrict_includes=True): | ||
662 | """Parse a manifest XML and return the computed nodes. | ||
663 | |||
664 | Args: | ||
665 | path: The XML file to read & parse. | ||
666 | include_root: The path to interpret include "name"s relative to. | ||
667 | parent_groups: The groups to apply to this projects. | ||
668 | restrict_includes: Whether to constrain the "name" attribute of includes. | ||
669 | |||
670 | Returns: | ||
671 | List of XML nodes. | ||
672 | """ | ||
655 | try: | 673 | try: |
656 | root = xml.dom.minidom.parse(path) | 674 | root = xml.dom.minidom.parse(path) |
657 | except (OSError, xml.parsers.expat.ExpatError) as e: | 675 | except (OSError, xml.parsers.expat.ExpatError) as e: |
@@ -670,10 +688,11 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md | |||
670 | for node in manifest.childNodes: | 688 | for node in manifest.childNodes: |
671 | if node.nodeName == 'include': | 689 | if node.nodeName == 'include': |
672 | name = self._reqatt(node, 'name') | 690 | name = self._reqatt(node, 'name') |
673 | msg = self._CheckLocalPath(name) | 691 | if restrict_includes: |
674 | if msg: | 692 | msg = self._CheckLocalPath(name) |
675 | raise ManifestInvalidPathError( | 693 | if msg: |
676 | '<include> invalid "name": %s: %s' % (name, msg)) | 694 | raise ManifestInvalidPathError( |
695 | '<include> invalid "name": %s: %s' % (name, msg)) | ||
677 | include_groups = '' | 696 | include_groups = '' |
678 | if parent_groups: | 697 | if parent_groups: |
679 | include_groups = parent_groups | 698 | include_groups = parent_groups |
@@ -681,13 +700,13 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md | |||
681 | include_groups = node.getAttribute('groups') + ',' + include_groups | 700 | include_groups = node.getAttribute('groups') + ',' + include_groups |
682 | fp = os.path.join(include_root, name) | 701 | fp = os.path.join(include_root, name) |
683 | if not os.path.isfile(fp): | 702 | if not os.path.isfile(fp): |
684 | raise ManifestParseError("include %s doesn't exist or isn't a file" | 703 | raise ManifestParseError("include [%s/]%s doesn't exist or isn't a file" |
685 | % (name,)) | 704 | % (include_root, name)) |
686 | try: | 705 | try: |
687 | nodes.extend(self._ParseManifestXml(fp, include_root, include_groups)) | 706 | nodes.extend(self._ParseManifestXml(fp, include_root, include_groups)) |
688 | # should isolate this to the exact exception, but that's | 707 | # should isolate this to the exact exception, but that's |
689 | # tricky. actual parsing implementation may vary. | 708 | # tricky. actual parsing implementation may vary. |
690 | except (KeyboardInterrupt, RuntimeError, SystemExit): | 709 | except (KeyboardInterrupt, RuntimeError, SystemExit, ManifestParseError): |
691 | raise | 710 | raise |
692 | except Exception as e: | 711 | except Exception as e: |
693 | raise ManifestParseError( | 712 | raise ManifestParseError( |
diff --git a/tests/test_manifest_xml.py b/tests/test_manifest_xml.py index f69e9cf8..6977b417 100644 --- a/tests/test_manifest_xml.py +++ b/tests/test_manifest_xml.py | |||
@@ -298,8 +298,8 @@ class IncludeElementTests(ManifestParseTestCase): | |||
298 | # Check level2 proj group not removed. | 298 | # Check level2 proj group not removed. |
299 | self.assertIn('l2g1', proj.groups) | 299 | self.assertIn('l2g1', proj.groups) |
300 | 300 | ||
301 | def test_bad_name_checks(self): | 301 | def test_allow_bad_name_from_user(self): |
302 | """Check handling of bad name attribute.""" | 302 | """Check handling of bad name attribute from the user's input.""" |
303 | def parse(name): | 303 | def parse(name): |
304 | manifest = self.getXmlManifest(f""" | 304 | manifest = self.getXmlManifest(f""" |
305 | <manifest> | 305 | <manifest> |
@@ -311,6 +311,34 @@ class IncludeElementTests(ManifestParseTestCase): | |||
311 | # Force the manifest to be parsed. | 311 | # Force the manifest to be parsed. |
312 | manifest.ToXml() | 312 | manifest.ToXml() |
313 | 313 | ||
314 | # Setup target of the include. | ||
315 | target = os.path.join(self.tempdir, 'target.xml') | ||
316 | with open(target, 'w') as fp: | ||
317 | fp.write('<manifest></manifest>') | ||
318 | |||
319 | # Include with absolute path. | ||
320 | parse(os.path.abspath(target)) | ||
321 | |||
322 | # Include with relative path. | ||
323 | parse(os.path.relpath(target, self.manifest_dir)) | ||
324 | |||
325 | def test_bad_name_checks(self): | ||
326 | """Check handling of bad name attribute.""" | ||
327 | def parse(name): | ||
328 | # Setup target of the include. | ||
329 | with open(os.path.join(self.manifest_dir, 'target.xml'), 'w') as fp: | ||
330 | fp.write(f'<manifest><include name="{name}"/></manifest>') | ||
331 | |||
332 | manifest = self.getXmlManifest(""" | ||
333 | <manifest> | ||
334 | <remote name="default-remote" fetch="http://localhost" /> | ||
335 | <default remote="default-remote" revision="refs/heads/main" /> | ||
336 | <include name="target.xml" /> | ||
337 | </manifest> | ||
338 | """) | ||
339 | # Force the manifest to be parsed. | ||
340 | manifest.ToXml() | ||
341 | |||
314 | # Handle empty name explicitly because a different codepath rejects it. | 342 | # Handle empty name explicitly because a different codepath rejects it. |
315 | with self.assertRaises(error.ManifestParseError): | 343 | with self.assertRaises(error.ManifestParseError): |
316 | parse('') | 344 | parse('') |