All vulnerabilities
CVE-2026-115774GHSA-2qrf-pqgx-qh9v

Twisted: Denial of Service in the Twisted IMAP4 Server via a Negative Literal Size in SEARCH

twisted TwistedpipLibraries & packages
CVSS
4.9Medium
ExploitationNo known exploitationNot in CISA KEV, which does not prove it is unexploitedCISA KEV
Exploit odds (EPSS)No scoreNo FIRST EPSS score in this snapshot yet
Fixed in26.10.0rc1Fixed versionGitHub
Published23 Oct 2026Updated 23 Oct 2026CVE List

What fixes it

  • 26.10.0rc1Twisted, pipGitHub
  • 26.10.0rc1twisted, PyPIOSVpip install "twisted>=26.10.0rc1"

A fixed version on one release line is not the upgrade target for every line. Check your exact version and any vendor backports.

Description

Summary

An infinite-loop vulnerability (CWE-835) in the Twisted IMAP4 server lets an authenticated client freeze the whole process with one small command. twisted.mail.imap4.parseNestedParens reads an IMAP literal size with int() and never rejects a negative value. A negative size such as {-100} drives the parser cursor negative, and because a negative Python index wraps around the end of the string instead of raising, the scanning loop never terminates. It runs synchronously on the single-threaded reactor, so one SEARCH command holds the reactor at full CPU forever and every other connection stops being served.

Details

Meeting an inline literal, parseNestedParens reads the size between the braces and advances the cursor past the body:

# twisted/mail/imap4.py  parseNestedParens
literalSize = int(s[i + 1 : end])          # no sign check
contentStack[-1].append((s[end + 3 : end + 3 + literalSize],))
i = end + 3 + literalSize                    # negative size -> i goes negative

int(b"-100") is -100, so i goes negative; the loop condition i < len(s) stays true and s[i : i + 1] on a negative index never raises, so the cursor cycles forever. The _literalStringLimit guard only covers the synchronizing-literal continuation path, not this inline branch. The call is reached from select_SEARCH through arg_searchkeys, dispatched only in the selected state, so a valid login and a selected mailbox (any low-privilege account) suffice. It runs in the reactor thread, and the connection timeout cannot fire because the reactor itself is frozen.

Affected product: Twisted (twisted.mail), the IMAP4 server twisted.mail.imap4.IMAP4Server Affected versions: 26.4.0 and earlier

PoC

Log in, select a mailbox, and send a SEARCH whose literal size is negative:

C: t1 LOGIN testuser password-test
C: t2 SELECT INBOX
C: t3 SEARCH {-100}zzzzzzzzzz

The server never responds and one core stays at 100 percent. In a measured run a health service co-hosted on the same reactor stopped answering the instant the command arrived, showing the freeze hits the whole process and not just the attacking connection. A valid {5}zzzzz completes normally and a non-numeric {abc} is rejected with a parse error, which isolates the cause to the missing negative-size check.

Impact

A remote denial of service. One command of about ten bytes freezes the single-threaded reactor, so it stops serving not only every other IMAP connection but every other service co-hosted in the same process (for example SMTP and POP3 under twistd mail), and no timeout can recover it until the process is restarted. It requires an authenticated session, so any account holder can trigger it; there is no confidentiality or integrity impact and no code execution.

Text from GitHub Advisory Database

Affected versions 53

  • Twisted, pip<= 26.4.0GitHub
  • twistedaffected: < 26.10.0rc1CVE List
  • twisted, PyPIfrom first release, fixed in 26.10.0rc1OSV
  • twisted, PyPI1.0.1OSV
  • twisted, PyPI1.0.3OSV
  • twisted, PyPI1.0.4OSV
  • twisted, PyPI1.0.5OSV
  • twisted, PyPI1.0.6OSV
  • twisted, PyPI1.0.7OSV
  • twisted, PyPI1.1.0OSV
  • twisted, PyPI1.1.1OSV
  • twisted, PyPI1.2.0OSV
  • twisted, PyPI10.0.0OSV
  • twisted, PyPI10.1.0OSV
  • twisted, PyPI10.2.0OSV
  • twisted, PyPI11.0.0OSV
  • twisted, PyPI11.1.0OSV
  • twisted, PyPI12.0.0OSV
  • twisted, PyPI12.1.0OSV
  • twisted, PyPI12.2.0OSV
  • twisted, PyPI12.3.0OSV
  • twisted, PyPI13.0.0OSV
  • twisted, PyPI13.1.0OSV
  • twisted, PyPI13.2.0OSV
  • twisted, PyPI14.0.0OSV
  • twisted, PyPI14.0.1OSV
  • twisted, PyPI14.0.2OSV
  • twisted, PyPI15.0.0OSV
  • twisted, PyPI15.1.0OSV
  • twisted, PyPI15.2.0OSV
  • twisted, PyPI15.2.1OSV
  • twisted, PyPI15.3.0OSV
  • twisted, PyPI15.4.0OSV
  • twisted, PyPI15.5.0OSV
  • twisted, PyPI16.0.0OSV
  • twisted, PyPI16.1.0OSV
  • twisted, PyPI16.1.1OSV
  • twisted, PyPI16.2.0OSV
  • twisted, PyPI16.3.0OSV
  • twisted, PyPI16.3.1OSV
  • twisted, PyPI16.3.2OSV
  • twisted, PyPI16.4.0OSV
  • twisted, PyPI16.4.1OSV
  • twisted, PyPI16.5.0OSV
  • twisted, PyPI16.5.0rc1OSV
  • twisted, PyPI16.5.0rc2OSV
  • twisted, PyPI16.6.0OSV
  • twisted, PyPI16.6.0rc1OSV
  • twisted, PyPI16.7.0rc1OSV
  • twisted, PyPI16.7.0rc2OSV
  • twisted, PyPI17.1.0OSV
  • twisted, PyPI17.1.0rc1OSV
  • twisted, PyPI17.5.0OSV

References 5