Doppelganger - Weaponizing CVE-2026-23066 at ZeroDay.Cloud
Faraz (Lead Blockchain Security Researcher · Zelic)
BSides Canberra IX (2026) · Main Track
Overview
In this highly technical presentation, Faraz, Lead Blockchain Security Researcher at Zelic, unveiled "Doppelganger," a novel exploit for CVE-2026-23066, a critical use-after-free vulnerability discovered in the Linux kernel's RXRPC subsystem. The talk, delivered at BSides Canberra, details the intricate process of identifying, analyzing, and ultimately weaponizing this bug to achieve a container escape and gain root privileges on an Ubuntu virtual machine. This exploit was successfully demonstrated at the Pwn2Own-like Zero Day Cloud competition in December 2025, showcasing a sophisticated attack chain against a hardened Linux environment.

Key moments
- 0:00 Introduction and Zero Day Cloud competition context
- 5:00 Bug hunting strategy: Kernel CTF diffing and stateful RXRPC
- 14:00 Linked list corruption fundamentals leading to use-after-free
- 18:20 The 'Doppelganger' race condition and specific UAF trigger
- 22:00 Extending race window using the Fallocate primitive
- 30:00 Reallocating freed memory with raw sockets for manipulation
- 36:00 Constructing arbitrary read/write primitives for root
- 46:00 Live demonstration of exploit achieving root
Doppelganger - Weaponizing CVE-2026-23066 at ZeroDay.Cloud
Speakers: Faraz, Lead Blockchain Security Researcher, Zelic
Conference: BSides Canberra
YouTube: https://www.youtube.com/watch?v=rQaxbBXquGU
Overview
In this highly technical presentation, Faraz, Lead Blockchain Security Researcher at Zelic, unveiled "Doppelganger," a novel exploit for CVE-2026-23066, a critical use-after-free vulnerability discovered in the Linux kernel's RXRPC subsystem. The talk, delivered at BSides Canberra, details the intricate process of identifying, analyzing, and ultimately weaponizing this bug to achieve a container escape and gain root privileges on an Ubuntu virtual machine. This exploit was successfully demonstrated at the Pwn2Own-like Zero Day Cloud competition in December 2025, showcasing a sophisticated attack chain against a hardened Linux environment.
The significance of the Doppelganger exploit extends beyond its successful competition demonstration. It serves as a compelling case study on the enduring relevance of manual code auditing, particularly for complex, stateful kernel subsystems that often evade the capabilities of even advanced fuzzing tools. Faraz meticulously breaks down the nuanced race conditions, linked list corruptions, and advanced memory manipulation techniques required to transform a use-after-free into a full root shell. The presentation offers invaluable insights into the mindset and methodology of top-tier kernel exploit developers, highlighting the challenges and innovative solutions employed to overcome modern kernel mitigations.
Background
▶ Watch: Introduction and Zero Day Cloud competition context (0:00)
The motivation for this deep dive into Linux kernel exploitation stemmed from the Zero Day Cloud competition, announced in late September 2025, with a final submission date of November 20th. The challenge was to escape a Docker container running on an Ubuntu VM and achieve root privileges on the host system by exploiting a Linux kernel vulnerability. At the time, Faraz was relatively new to Linux kernel exploitation, having only written one exploit for a known vulnerability. This presented a formidable personal challenge: find, fully exploit, and stabilize a zero-day vulnerability in less than a month, all while preparing for international travel.
Faraz teamed up with Pumpkin, and their initial strategy involved attack surface enumeration. Recognizing the popularity and hardened nature of Google's Kernel CTF instances, they decided to diff the kernel attack surface between Kernel CTF and the target Ubuntu environment. The goal was to identify kernel components accessible only on Ubuntu, hypothesizing that these would be less scrutinized by the broader security community. Pumpkin developed a script to test various socket types, and the comparison yielded a large list of potential targets. Faraz spent a week and a half manually auditing code, initially finding "useless bugs" like a null pointer dereference and an out-of-bounds heap read, which, while not exploitable for privilege escalation, confirmed his bug-hunting methodology was on the right track.
A critical turning point came when Faraz stepped back to analyze the characteristics of useful versus useless bugs. He consulted Google's syzkaller dashboard, a fuzzer that runs 24/7 on various kernel targets. His analysis of 100-150 recent submissions revealed that most bugs were not related to race conditions and were not in stateful kernel attack surface. This insight led him to focus on these two areas: race conditions, which are difficult for fuzzers to find, and highly stateful code, which is hard for fuzzers to reach effectively. This refined strategy quickly pointed to RXRPC as a prime target. RXRPC is an RPC protocol implemented in the Linux kernel, characterized by its complex state machine, asynchronous events, and operations across multiple contexts (soft IRQ, work queue, and syscall), making it inherently prone to race conditions and difficult for automated tools to navigate.
Key Findings
▶ Watch: Linked list corruption fundamentals leading to use-after-free (14:00)
The core of the Doppelganger exploit lies in CVE-2026-23066, a critical use-after-free (UAF) vulnerability discovered in the Linux kernel's RXRPC subsystem. This vulnerability is a direct result of a linked list corruption triggered by a nuanced race condition. The fundamental discovery is that under specific, racey conditions, the same RXRPC_call structure can be inserted into the receive message queue of an RXRPC socket multiple times. This double insertion corrupts the next and previous pointers of the RXRPC_call object, leading to a state where the object is logically still part of the doubly linked list, but its internal pointers indicate it is unlinked.
The "Doppelganger" phenomenon occurs when the kernel attempts to remove this corrupted RXRPC_call object from the list. Due to its internal pointer corruption, the unlinking operation fails silently, leaving the object's reference in the list intact. However, the kernel code assumes the object has been successfully removed and proceeds to decrement its reference count and eventually free the memory associated with the RXRPC_call. This creates a classic use-after-free scenario, as the receive message queue now holds a dangling pointer to freed kernel memory. Subsequent traversal or manipulation of this queue would then access this freed memory, providing a powerful primitive for arbitrary memory manipulation.
Technical Deep Dive
▶ Watch: Extending race window using the Fallocate primitive (22:00)
The RXRPC subsystem manages RPC calls, represented by the RXRPC_call structure. Both client and server sockets maintain a receive message queue, which is a doubly linked list protected by a receive message lock. The vulnerability hinges on a race condition between two key functions operating in different kernel contexts:
RXRPC_notify_socket(Soft IRQ context): This function is called when a client sends an RPC call to a server. It's responsible for duplicating theRXRPC_calland inserting it into the server'sreceive message queue. Crucially, it includes a check (list_empty) to ensure the call is not already on a list before inserting it.rx_rpc_receive_message(Syscall context): This function, invoked viareceivesyscalls, processesRXRPC_callobjects. At its beginning, it removes the call from the queue for processing. If processing is incomplete, a specific code path,error_requeue_call, callslist_addto re-add the call to thereceive message queue. The critical flaw here is the absence of alist_emptycheck before re-adding the call.
The Doppelganger race condition unfolds as follows:
- Thread 0 (Syscall): Calls
rx_rpc_receive_message, which removescall Xfrom the list to process it.call Xis now temporarily off the list. - Thread 1 (Soft IRQ): Simultaneously, the client spams the server with
call X.RXRPC_notify_socketcheckslist_emptyforcall X. Sincecall Xis currently off the list, this check passes, andcall Xis inserted into thereceive message queue. - Thread 0 (Syscall): Finishes processing and, without checking
list_empty, callslist_addto re-addcall Xto the queue.call Xis now effectively in the list twice, leading to linked list corruption. Specifically,list_add_tail(orlist_add) on an already-linked node causes itsnextandpreviouspointers to point back to itself, while the list head still points to it.
This corruption creates the UAF: when call X is later removed, the kernel assumes its pointers are correct. Because X->next and X->prev point to X itself, the unlinking operation fails to update the list head's pointers, meaning call X remains logically in the list even after being freed.
Exploiting this UAF required overcoming several significant challenges:
- Winning the Race: The race window for the double insertion is extremely short. To extend it, Faraz employed a technique based on
fallocate. Theput_cmsgfunction withinrx_rpc_receive_messagewrites user ID data back to userland. By allocating a userland buffer withfallocateand then deallocating its physical pages just beforeput_cmsgattempts to write to it,put_cmsgis forced to sleep until the page deallocation completes. This pauses the syscall context, extending the window during whichcall Xis off the list, allowing the soft IRQ thread to successfully re-insert it.
- Detecting the Win: After triggering the race, the exploit needs to confirm that the list corruption occurred. A correctly processed
call Xshould be removed from the queue. If, after processing, thepollAPI (available in RXRPC for querying thereceive message queue) still indicatescall Xis present, the race was won, and the list is corrupted.
- Freeing the Corrupted Call: The client spamming for the race condition causes
call X's reference count to skyrocket (over 1000). The exploit cannot simply free it.
- Reference Count Leak: The
/proc/net/rxrpc/callsfile conveniently exposes the reference counts of allRXRPC_callobjects, allowing the exploit to determine the exact count. - Controlled Decrement: Calling
receiveon the socket with an invalid (e.g., null) buffer pointer causesrx_rpc_receive_messageto decrementcall X's reference count by one viaRXRPC_put_calland not requeue it. This primitive is used to reduce the reference count down to two. - Final Free: Once the count is two, a final "correct" processing of
call Xdecrements the count by two, leading toRXRPC_release_calland the actual freeing of the object, while it remains referenced in the queue.
- Heap Spray and Reallocation: The freed
RXRPC_callmemory needs to be reallocated with a controlled object.
- Cross-Cache Reallocation:
RXRPC_callobjects reside in a specific slab cache. To reallocate its physical page with a different object type, cross-cache reallocation is necessary. This involves freeing manyRXRPC_callobjects to return their physical pages to the kernel's general page allocator. - Target Object: Raw Sockets: Within a Docker container, the exploit has root privileges, allowing the creation of
SOCK_RAWsockets. These sockets have anRX ring bufferwhich is a page of memory that can be mapped directly into userland with read/write access. By sprayingRX ring bufferpages, one of them will land on the freedRXRPC_callmemory. This provides a userland read/write primitive over theRXRPC_callstructure, which is still pointed to by thereceive message queue.
- Kernel Heap Leak (Address of Corrupted Call): The newly mapped raw socket page is zeroed.
- To get the address of
call X, a newcall Yis added to the corrupted list. ReadingX->nextvia the raw socket mapping leaksY's kernel address. - Then,
X->nextandX->prevare overwritten (via the raw socket mapping) to point toY. - Processing
Xagain causes its pointers to reset to point toXitself. ReadingX->next(orX->prev) through the raw socket mapping now revealsX's own kernel address.
- Arbitrary Read Primitive (Kernel Text Leak):
RXRPC_callobjects also have their ownreceive message queuefor SKBs (socket buffers).- The exploit overwrites
X's memory (via raw socket) to construct a fake SKB structure. X->msg_queue.nextis set to point to this fake SKB.- In the fake SKB,
lenis set (e.g.,0x1000), and adata_pointeris set to the desired kernel address to read from. - Calling
receiveonXtriggers the kernel to process the fake SKB, readinglenbytes fromdata_pointerand returning them to userland. - Finding Kernel Text Base: To get a kernel text leak, the exploit uses the arbitrary read to scan backwards from
X's address until it hits thepage_offset_base(kernel heap base). This is detected by checking(page_offset_base + 0x9C000), which consistently holds a physical address pointing to the kernel's BSS region. - Kernel Text Double Mapping: Adding the
kernel_heap_base_VAto the leakedBSS_physical_addressyields a virtual address within the kernel text double mapping. This region contains a mapping of all physical pages, including the kernel text/BSS segments. - IDT Leak: From the BSS segment, the Interrupt Descriptor Table (IDT) is read. The first entry,
ASM_XC_divide_errorfunction pointer, provides a direct pointer into the kernel's text segment, effectively bypassing KASLR.
- Arbitrary Write Primitive:
- The
rx_rpc_receive_messagefunction dereferencescall->securityand then callscall->security->verify_packet. - Since
call Xis controlled,call->securitycan be faked withinX's memory, and theverify_packetfunction pointer can be set to an arbitrary kernel function. - Pumpkin identified
__seq_putsas a suitable target function. This function takes two arguments: a structure (which can be ourcall X) and a string (which can be our fake SKB). It thenmemcpys data from the string into abuff_pointerwithin the structure. - The exploit overwrites
call->security->verify_packetto point to__seq_puts. - It then overwrites
X's memory (via raw socket) to set thebuff_pointerto the address ofcore_pattern(obtained from the kernel text leak). - Finally, the fake SKB's data is set to the desired string to overwrite
core_pattern(e.g., a path to a shell script).
- Root Shell:
- The
core_patternkernel string specifies a program to execute when a userland process crashes. This program is executed with full root privileges by the kernel. - By overwriting
core_patternwith a path to a controlled shell script (e.g.,/tmp/shell.sh) using the arbitrary write primitive, the exploit can then intentionally crash itself. - The kernel, upon detecting the crash, will execute
/tmp/shell.shas root, thereby achieving a container escape and a root shell on the host system.
Demo / Proof of Concept
▶ Watch: Reallocating freed memory with raw sockets for manipulation (30:00)
The Doppelganger exploit was successfully demonstrated live on stage at the Zero Day Cloud competition. The demonstration involved setting up a Docker container on an Ubuntu virtual machine, which served as the target environment. Inside this container, the exploit was executed. Upon successful execution, the screen displayed custom ASCII art of the team name, "Team Capsicum Cream Cake," visually confirming the successful container escape and acquisition of root privileges on the underlying Ubuntu VM. This live demonstration underscored the exploit's stability and effectiveness in a real-world competition scenario.
Defensive Implications
▶ Watch: Live demonstration of exploit achieving root (46:00)
The Doppelganger exploit highlights several critical areas for defenders:
- Patching and Updates: The most immediate defensive action is to ensure all systems are patched against CVE-2026-23066 once a fix is released. Regular and timely kernel updates are paramount.
- Code Auditing for Stateful Subsystems: The discovery of this vulnerability in RXRPC underscores that complex, stateful kernel subsystems are high-value targets for manual code auditing. Their intricate logic and multi-context operations make them fertile ground for race conditions and logic bugs that automated fuzzers often miss. Developers and security researchers should prioritize these areas for thorough review.
- Linked List Safety: The exploit's reliance on linked list corruption emphasizes the need for robust linked list handling in kernel code. Developers should rigorously ensure that nodes are only added to a list if they are not already linked and that unlinking operations correctly update all relevant pointers. The absence of a
list_emptycheck beforelist_addinrx_rpc_receive_messagewas a critical omission. - Container Hardening: While Docker containers provide isolation, kernel vulnerabilities can bypass them. Defenders should employ stringent container hardening measures, including:
- Capability Dropping: Minimize the capabilities granted to containers. For instance, the ability to create
SOCK_RAWsockets, which was crucial for the heap spray, requiresCAP_NET_RAWandCAP_NET_ADMIN. Dropping these and other unnecessary capabilities (likeCAP_SYS_ADMINforfallocatemanipulation) can significantly reduce the attack surface. - Seccomp Profiles: Implement strict seccomp profiles to restrict syscalls available to containerized applications, further limiting potential exploit primitives.
core_patternHardening: Thecore_patternmechanism, while useful for debugging, is a frequently abused primitive for privilege escalation. System administrators should ensurecore_patternis set to a secure, non-writable location or a trusted, minimal handler.- Kernel Mitigations (KASLR, SMAP/SMEP): While not explicitly broken in the talk, the exploit demonstrates techniques to bypass KASLR (kernel address space layout randomization) through targeted information leaks (e.g., IDT pointers from the kernel text double mapping). Continued research into strengthening these mitigations and developing new ones is essential.
- Attack Surface Reduction: If specific kernel modules or subsystems like RXRPC are not strictly necessary for a given system's operation, they should be disabled or removed to minimize potential attack vectors.
Key Takeaways
- Manual Auditing for Complex Code: Highly stateful kernel subsystems and intricate race conditions remain difficult targets for automated fuzzers, highlighting the enduring value of skilled manual code auditing.
- Advanced Exploitation Techniques: Modern kernel exploitation requires a sophisticated understanding of subtle primitives, such as
fallocate-based race window extension, cross-cache reallocation, and leveraging kernel text double mappings for KASLR bypass and arbitrary read/write. - Linked List Corruption is Potent: Errors in linked list management, particularly double insertions or improper unlinking, can lead to powerful use-after-free conditions that provide arbitrary memory access.
- Container Escapes via Kernel Bugs: Even with containerization technologies like Docker, critical kernel vulnerabilities can lead to full system compromise, enabling attackers to escape the container and gain root privileges on the host.
core_patternas a Privilege Escalation Vector: Thecore_patternkernel mechanism is a common and effective target for achieving arbitrary code execution as root once an arbitrary write primitive is established.- Strategic Attack Surface Enumeration: Diffing hardened kernel environments (like Kernel CTF) against target environments (like Ubuntu) can be an effective methodology for identifying unique and potentially less-audited attack surfaces.
About the Speaker(s)
Faraz is currently a Lead Blockchain Security Researcher at Zelic, a company based in the US. Prior to his work at Zelic, he specialized as a Chrome and Android vulnerability researcher, a field he now pursues in his free time for enjoyment. Faraz is also actively involved in the CTF community, being part of the "skateboarding dog CTF team" and running CTF challenges at conferences like BSides Canberra. His expertise spans a wide range of security domains, from browser and mobile vulnerabilities to advanced kernel exploitation.
Reviews
Dr. Zero (Offensive Security Researcher) — MUST SEE
This is the real thing — a full zero-day to root exploit chain developed under competition pressure, with every step documented. Faraz doesn't just show the CVE, he walks through the entire exploit development process: the attack surface diffing strategy, the fallocate race window extension trick, cross-cache spraying with raw socket ring buffers, and the kernel text double mapping for KASLR bypass. The kind of talk that makes you want to go audit kernel code.
Heather Calloway (CISO) — STRONG ACCEPT
Elite kernel exploitation work demonstrating a container escape via a novel Linux use-after-free. The methodology is sound, the execution is impressive, and every CISO running containerized workloads on Linux needs to understand that kernel bugs bypass your container isolation model entirely.