RC RANDOM CHAOS

IOCCC 2025 ships glibc tcache poisoning primitives

The 29th IOCCC's 2025 winners distill heap metadata corruption, tcache poisoning, and type confusion into legal C - the same primitives behind modern CVEs.

· 6 min read

The 29th International Obfuscated C Code Contest closed its 2025 cycle with a winner set that reads like a catalogue of memory corruption primitives rendered in legal ISO C. The contest is not a security event. The submissions are not exploits. The mechanisms they demonstrate are identical to the ones that ship as CVEs every quarter. That is the analytical value. IOCCC entries strip a bug class down to the C language feature that enables it, then weaponise that feature against the reader’s ability to parse the program. A vulnerability researcher reads these the same way a chess player reads endgame studies. The position is artificial. The motifs are real.

The bug class that dominates the 2025 winning pool is allocator state manipulation. Multiple entries chain malloc and free calls whose ordering and size arguments are derived through pointer arithmetic on string literals, preprocessor token pasting, and __LINE__ evaluations at translation time. The control flow that decides the heap layout is computed at compile time and obscured at read time. The runtime behaviour is deterministic. The reader cannot see it. This pattern is the same primitive that drives glibc tcache poisoning. The attacker controls which freed chunks land where in the per-thread cache, then forces the next allocation of a matching size class onto an attacker-chosen address. CWE-416, use-after-free. CWE-122, heap-based buffer overflow. CWE-843, type confusion via reused storage. The IOCCC entries do not exploit these. They show how cleanly the C language permits the preconditions for them to be constructed.

The mechanism is worth being precise about. glibc’s ptmalloc2 maintains size-class bins for freed chunks. The tcache is a per-thread singly linked list of recently freed chunks under 0x420 bytes by default on glibc 2.34 and later, with a key field added to defend against double-free. A tcache poisoning primitive requires a single overwrite of the fd pointer in a freed chunk’s header. The next two allocations of that size class return the original chunk and then an attacker-chosen address parsed as a chunk. The 2025 IOCCC winners that touch the heap construct precisely this kind of dance. The free order is hidden behind macro recursion. The allocation sizes are computed from the parity of the program’s own source bytes counted at runtime through argv[0] reads. The reader watching execution sees a normal stream of allocations. The reader auditing the source sees a stream of (*p)[i] accesses that do not visibly correlate with the allocator. The two views do not reconcile without ASan or rr replay. That gap is the entire point.

A second motif across the 2025 set is type confusion through union aliasing and pointer punning. Entries declare a union of a function pointer and a size_t, populate the integer member through arithmetic on &main, then call through the function pointer member. ISO C permits the storage. The standard does not promise the value survives the cast intact under optimisation. The submissions exploit the fact that, on the platforms IOCCC judges against, it does. The same primitive in a real target is CVE-2023-4863 territory - the libwebp heap overflow that Apple and Google patched as an actively exploited zero-day, where a Huffman table allocation undersized through an integer miscalculation produced a write-what-where into adjacent heap metadata. The IOCCC entries demonstrate the language-level operation. The CVE demonstrates the same operation reached through a parser handling attacker-controlled input.

The exploit path from the IOCCC pattern to a real-world primitive is short. The contest entries assume the attacker controls the source. A real target hands that control to whoever supplies the input the program parses. The translation step is the parser. Image decoders, font rasterisers, JavaScript JITs, kernel ioctl handlers - each converts attacker bytes into structured allocations whose sizes, types, and lifetimes are derived from those bytes. When the derivation contains an integer truncation, a sign confusion, or a missed bounds check, the parser becomes the obfuscation layer. The attacker’s input is the IOCCC source. The vulnerable program is the compiler. The heap layout falls out the other side.

The 2025 cohort also includes entries that abuse setjmp and longjmp for non-local control flow obscuration. The mechanism is legitimate. The use produces control flow graphs that static analysers cannot follow without inter-procedural alias resolution. The parallel in offensive tooling is direct. Stack pivoting via ROP, signal-handler-driven control flow, and continuation-passing in JIT-spray payloads all rely on the same observation: a control transfer that the analyser cannot model is a control transfer that the defender cannot alert on. CFI mitigations on Linux and Windows, including Intel CET shadow stacks and Microsoft’s XFG, address forward-edge and backward-edge transfers under specific conditions. longjmp operates within those conditions when used as the standard defines. An attacker who reaches a longjmp whose jmp_buf is corrupted gets a controlled jump that the shadow stack permits. CVE-2022-23219 in glibc and the long history of jmp_buf corruption in CTF challenges trace this pattern. The IOCCC entries place it inside a legitimate program with no malicious intent. The mechanism does not care.

Real-world exploitation context matters here. Tcache poisoning is the dominant heap exploitation technique against modern glibc targets as of the current patch level. It appears in the public exploitation writeups for CVE-2021-3156 sudo Baron Samedit follow-on chains, in kernelCTF submissions against the Linux SLUB allocator’s analogous freelist, and in browser renderer exploitation where partitionalloc-equivalent freelist corruption produces the same primitive in a different allocator. The IOCCC entries are not weapons against any of these. They are a distilled demonstration of the language-level moves that, combined with a parser, produce the weapon.

In telemetry, none of this fires anything. That is the point worth stating clearly. A program executing legal ISO C, performing balanced malloc/free pairs, calling through function pointers it has the right to call, and using setjmp/longjmp for control flow generates no Sysmon events of interest, no EDR behavioural alerts, no SIEM correlations. Sysmon Event ID 10, ProcessAccess, fires on cross-process handle acquisition - not on intra-process heap manipulation. EDR memory scanners look for known shellcode signatures, RWX page allocations, and process hollowing patterns under MITRE T1055.012. They do not look for fd pointer overwrites in tcache chunks. GWP-ASan, the sampling allocator deployed in Chrome, Android, and recent glibc builds, catches a small fraction of heap corruptions probabilistically. AddressSanitizer catches all of them but is a development-time tool, not a production control. The detection gap is structural. The exploitation primitive lives below the abstraction layer where defensive telemetry operates.

The attack chain mapping is straightforward where IOCCC patterns recur in real targets. T1190, exploitation of a public-facing application, when the parser is network-reachable. T1203, exploitation for client execution, when the parser is a renderer or media decoder. T1068, exploitation for privilege escalation, when the parser is a kernel ioctl or a setuid binary. The post-exploitation primitive in each case is arbitrary read/write inside the target process. The IOCCC entry shows how cheaply that primitive constructs from the C abstract machine. The CVE shows what an attacker-controlled input stream looks like once it reaches that machine through a parser written without the discipline the abstract machine requires.

The technical reality post-IOCCC is that the contest does not change patch boundaries. It does not introduce new CVEs. What it does is publish, in a compact and reviewable form, the precise language constructs that real exploitation depends on. Vulnerability researchers who study the 2025 winners gain pattern recognition for unsafe pointer arithmetic, allocator state manipulation, and union-based type confusion that transfers directly to code review of production C codebases. Detection engineers gain a clear statement of what their telemetry cannot see - heap metadata corruption, freelist manipulation, and control flow transitions through legitimate language features. The residual exposure is the same exposure that has persisted in C codebases since the standard was written. The contest documents it. The CVEs prove it. The 2025 winners are a teaching set for how attackers think when they read source.

Share

Keep Reading

Stay in the loop

New writing delivered when it's ready. No schedule, no spam.