Cypherpunk frees the key schedule twice
UAF in the Cypherpunk Library's context teardown - CWE-416, heap reuse, sandbox-free RCE path, and why EDR misses the corruption stage.
Use-after-free in cryptographic libraries does not get the attention the math does. Every audit cycle spent on constant-time scalar multiplication, RNG entropy, and side-channel resistance is correct work. None of it matters when the surrounding C code frees a context struct twice and the heap allocator hands the chunk back to an attacker-controlled allocation path. The Cypherpunk Library - used inside several open-source crypto stacks for envelope encryption and key exchange wrapping - exposes exactly this pattern. The bug class is CWE-416. The exploit primitive is a controlled UAF on the cipher context structure. The advisory does not lead with this. The patch diff does.
The mechanism is straightforward. The library exposes a context handle returned by an initialization call - cypherpunk_cipher_new() or equivalent - and expects callers to release it via cypherpunk_cipher_free(). Internally the context holds pointers to allocated subobjects: a key schedule, an HMAC state, an aux buffer for AEAD tag computation. When an error path inside an update or finalization routine triggers cleanup, the subobjects are released. The outer context pointer is not nulled. The caller, unaware that the inner state has been torn down, calls the next update routine. That routine dereferences the dangling subobject pointer. The chunk previously holding the key schedule has been returned to the heap freelist. Anything an attacker can place there now controls the dereference.
That is the primitive. From there the exploit path depends on what the attacker sprays into the freed chunk and what the dereference accesses. If the key schedule slot is reallocated to an attacker-controlled buffer carrying a function pointer used in a subsequent indirect call, the dereference flows into controlled flow. Modern allocators - glibc’s tcache, jemalloc’s small-size class freelists - make this reuse deterministic when the attacker controls allocation sizing on the surrounding application surface. Cryptographic libraries are particularly exposed here because they allocate predictable sizes for fixed-width schedules. AES-128 key schedule is 176 bytes. AES-256 is 240. Both fall into well-known small-size bins. Reuse is reliable.
The exploitation chain depends on where the library sits. The larger exposure is a server-side TLS or message-handling path where attacker-controlled traffic drives the allocator state. A network-facing daemon parsing untrusted ciphertext, calling into the library, and encountering the error path that triggers premature subobject teardown gives a remote attacker the freed-chunk timing the chain needs. The next allocation request - driven by the same message or the next message in the connection - lands in the freed slot. The attacker now controls the bytes the library dereferences. From there the work is shaped by the dereference site. A pointer-to-function dispatch through the freed structure transfers control. A pointer-to-pointer write inside the freed structure becomes an arbitrary write. Either is enough to pivot toward ROP through the library’s PLT, against gadgets exposed by the linked crypto and libc symbol surface.
This is not theoretical territory. The pattern is well documented in adjacent libraries. OpenSSL has shipped UAF fixes across its history - SSL_set_session paths, X509 verification cleanup in older 1.0.x releases. GnuTLS has had double-free conditions in session ticket handling. NSS has been hit on RSA key teardown. The Cypherpunk Library inherits the same class of bug because it inherits the same memory management idiom: manual reference counting on context structures, error paths that do not coordinate with the outer caller’s lifecycle assumptions. The patch model is identical - null the freed pointer, increment a state field that downstream calls validate, or split the context into immutable and mutable halves so teardown does not leave dangling internal handles.
What an attacker does with this depends on access. In a client-side context - a CLI tool processing an attacker-supplied ciphertext file, a desktop application decrypting an attacker-supplied attachment - exploitation maps to MITRE T1203, exploitation for client execution. The user opens the file. The library is invoked. The UAF triggers. Code runs in the user’s context. Persistence and discovery follow normal post-exploitation paths. In a server-side context - a daemon decrypting inbound traffic, a message broker validating signatures, an API gateway unwrapping JWE payloads - the same primitive becomes T1190, exploit public-facing application. Code runs as the service account. If the service runs as root, kernel attack surface is one syscall away. If it runs constrained, the attacker still has remote code execution in the service context and pivots from there.
Threat actor uptake follows reliability. UAFs in C crypto libraries reach exploit chains that already have the harness work done - heap grooming, ROP gadget databases, syscall enumeration. The chains used against OpenSSL CVEs over the last decade can be re-pointed at the Cypherpunk Library with modest modification because the allocator, the ABI, and the gadget surface are the same. There are no publicly attributed campaigns linked to this specific library at time of writing. That absence does not imply absence of exploitation. It implies absence of attribution. Exploitation of a memory corruption bug in a server-side crypto library is the kind of activity that surfaces months later through incident response forensics, not through real-time telemetry.
That telemetry gap matters. UAF exploitation against a userland service rarely produces a clean signal in EDR. The dereference happens inside the library’s address space, on a thread already owned by the legitimate process. Sysmon Event ID 1 will not fire - no new process. Event ID 10 will not fire unless a later stage of the chain touches LSASS or another monitored process. The Windows Security log produces nothing. On Linux, auditd sees syscalls only after the attacker has converted memory corruption into something that crosses the userland-kernel boundary - execve, mmap with PROT_EXEC, mprotect flipping a data page to executable. Those events fire late. The corruption happened earlier inside the process heap and left no kernel-visible trace.
Where defenders catch this is in the second-order signals. A daemon that crashes and respawns with no application-level explanation is the first indicator. Most heap corruption attempts fail multiple times before they succeed - the attacker iterates allocator state, sprays, retries. Repeated crashes under glibc’s malloc detection, abort() calls with “double free or corruption” or “free(): invalid pointer,” and segfaults at addresses inside the library’s mapped range are the telemetry. ASLR randomizes the base. The offset patterns inside the library remain stable. SIEM correlation on repeated SIGSEGV from the same binary, weighted against connection counts from the upstream source, surfaces the grooming pattern. Most environments do not have this correlation rule. That is the gap.
EDR catches this class of bug through behavioural detection on the conversion stage - when the corrupted process attempts to spawn a child, load a remote module, or call mprotect to flip a page executable. CrowdStrike, SentinelOne, and Microsoft Defender for Endpoint all flag this conversion. The window between memory corruption and conversion is where the attacker either succeeds or stalls. For a chain with a clean ROP-to-shellcode-to-execve pivot, that window is microseconds. For a chain that pauses to enumerate, leak pointers, and stage a second payload, it is longer. Defenders who tune for the conversion catch the latter. The former requires upstream controls - CFI, allocator hardening, library-side fixes.
The patch boundary is narrow. The fix nulls the freed subobject pointers and adds a state field on the outer context that downstream routines validate before dereferencing inner state. Versions prior to the patched release remain exposed. Downstream consumers that vendor the library - common in commercial security products that ship their own crypto stack - inherit the bug until they rebase. SBOM data for affected versions identifies exposure. Most organisations do not query SBOMs for transitive crypto library inclusion. That is the residual exposure post-patch. The math was never the problem. The C code around it was.
Keep Reading
iis-securityScStoragePathFromUrl overflows the stack on PROPFIND
CVE-2017-7269 turns an unpatched IIS 6.0 WebDAV server into pre-auth RCE. The exploit primitive, the telemetry blind spot, and the residual exposure.
vulnerability-researchNetScaler trusts snprintf, leaks adjacent heap memory
Why 'silent' vulnerabilities like Citrix Bleed (CVE-2023-4966) are already exploited at the network edge, what they produce in telemetry, and where defenders are blind.
rsyncmemcpy walks off the end of the receiver
rsync shipped six CVEs in January 2025. LLMs did not write new bugs - they compressed variant discovery, harness generation, and vulnerable deployment.
Stay in the loop
New writing delivered when it's ready. No schedule, no spam.