浏览代码

detect and reject ref-deltas which use an object as its own delta base

Dulwich could be driven into an infinite loop by creating a pack file
which contains a ref-delta that refers to the deltified object itself
as the delta's base, indexing this pack file as a thin-pack, and then
trying to access the packed object.

Fix this problem and add a test case which triggers the issue if the
fix is reverted (caution: the test will loop forever).

Found via a bug in the Software Heritage Git Loader test suite which
created such a pack file:
https://gitlab.softwareheritage.org/swh/devel/swh-loader-git/-/merge_requests/184/
Stefan Sperling 9 月之前
父节点
当前提交
a2258c61a6
共有 2 个文件被更改,包括 32 次插入0 次删除
  1. 2 0
      dulwich/pack.py
  2. 30 0
      tests/test_pack.py

+ 2 - 0
dulwich/pack.py

@@ -2655,6 +2655,8 @@ class Pack:
                 assert isinstance(basename, bytes) and len(basename) == 20
                 base_offset, base_type, base_obj = get_ref(basename)
                 assert isinstance(base_type, int)
+                if base_offset == prev_offset:  # object is based on itself
+                    raise UnresolvedDeltas(sha_to_hex(basename))
             delta_stack.append((prev_offset, base_type, delta))
 
         # Now grab the base object (mustn't be a delta) and apply the

+ 30 - 0
tests/test_pack.py

@@ -1284,6 +1284,36 @@ class DeltaChainIteratorTests(TestCase):
         except UnresolvedDeltas as e:
             self.assertEqual((sorted([b2.id, b3.id]),), (sorted(e.shas),))
 
+    def test_ext_ref_deltified_object_based_on_itself(self):
+        b1_content = b"foo"
+        (b1,) = self.store_blobs([b1_content])
+        f = BytesIO()
+        build_pack(
+            f,
+            [
+                # b1's content refers to bl1's object ID as delta base
+                (REF_DELTA, (b1.id, b1_content)),
+            ],
+            store=self.store,
+        )
+        fsize = f.tell()
+        f.seek(0)
+        packdata = PackData.from_file(f, fsize)
+        indexfile = BytesIO()
+        packdata.create_index(
+            "test.idx",
+            version=2,
+            resolve_ext_ref=self.get_raw_no_repeat,
+        )
+        indexfile.seek(0)
+        packindex = load_pack_index("test.idx")
+        pack = Pack.from_objects(packdata, packindex)
+        try:
+            # Attempting to open this REF_DELTA object would loop forever
+            pack[b1.id]
+        except UnresolvedDeltas as e:
+            self.assertEqual((b1.id), e.shas)
+
 
 class DeltaEncodeSizeTests(TestCase):
     def test_basic(self):