Stage ROP payloads and fake kernel structures at a KASLR-independent fixed virtual address by exploiting the x86-64 cpu_entry_area (CEA). Address formula: CEA_BASE + cpu * 0x3b000 + 0x1f58 (derive CEA_BASE from /proc/kallsyms or kernel binary). Write mechanism: fork a child, pin it to the target CPU, load payload into all 15 GPRs from a userspace buffer, then trigger a #DE fault (divq unmapped_addr) or #UD (ud2). The kernel exception entry path (PUSH_AND_CLEAR_REGS / error_entry) saves the registers onto the CEA exception stack, writing 120 bytes of attacker-controlled data to the fixed address. Used in 17 kernelCTF CVE directories (2023-2025) spanning nftables, tc/Qdisc, and file_operations exploits. Requires kernel before 6.4 (CVE-2023-0597 fix). Key prerequisite: setsid() before fault.
Use this technique when you need to place a fake kernel structure (ops table, ROP chain anchor) at a known virtual address without a heap infoleak. It is applicable when:
The write produces exactly 120 bytes (15 GPRs × 8 bytes) at a fixed address. No heap spray alignment, no infoleak of the staging location required.
The x86-64 kernel maps a cpu_entry_area per CPU at a fixed virtual address
not covered by KASLR (CVE-2023-0597). When any hardware exception (#DE, #UD)
fires from user mode, the CPU exception entry assembly (error_entry /
PUSH_AND_CLEAR_REGS in arch/x86/entry/entry_64.S) pushes all 15 GPRs onto
the per-CPU exception stack inside the CEA at offset 0x1f58 from the CEA base.
The exploit technique: load all 15 GPRs with payload values by pointing RSP at a userspace payload buffer and executing 15 instructions, then trigger the fault. The kernel "saves" our controlled values, writing them to the CEA at a predictable address. A signal handler suppresses the crash.
popFixed address for CPU 1 (used by most exploits):
PAYLOAD_LOCATION(1) — derive from the CEA base address plus cpu * 0x3b000 + 0x1f58 (CEA base is fixed pre-6.4; derive from /proc/kallsyms or kernel binary)
bypass_kaslr()) — payload contains kbase + gadget_offsetsched_setaffinity() to HELPER_CPU (typically CPU 1)setsid() — detaches from terminal, prevents panic propagationdivq targeting an unmapped address)sleep(1) then proceed; the payload is now resident in kernel CEAPAYLOAD_LOCATION(HELPER_CPU)Address macros and payload struct (source: CVE-2024-27397_mitigation/exploit.c L160-177, CVE-2023-4244_mitigation/exploit.c L260-271):
Define CPU_ENTRY_AREA_BASE(cpu) as the fixed CEA base address (derive from /proc/kallsyms or kernel binary) plus cpu multiplied by 0x3b000. Define PAYLOAD_LOCATION(cpu) as CPU_ENTRY_AREA_BASE(cpu) plus 0x1f58. Set HELPER_CPU to 1. Declare a payload struct as a union of a named struct with three uint64_t fields (r15 slot for the RIP control gadget, r14 slot for the stack pivot gadget in Gen-2b, r13 slot for the return gadget in Gen-2b) and a raw uint64_t array of 16 elements covering all GPR slots.
Write function (source: CVE-2024-27397_mitigation/exploit.c L386-410, CVE-2023-4244_mitigation/exploit.c L742-765):
Implement a noreturn function write_cpu_entry_area(void *payload) using inline assembly. Set RSP to the payload pointer, then execute 15 consecutive pop instructions in register order (r15, r14, r13, r12, rbp, rbx, r11, r10, r9, r8, rax, rcx, rdx, rsi, rdi) to load all GPRs with the payload values. Then execute divq targeting an unmapped address to trigger a #DE fault — the kernel exception entry path will push all GPRs onto the CEA exception stack, writing the 120-byte payload to PAYLOAD_LOCATION(HELPER_CPU). End with an infinite loop. Call __builtin_unreachable() to satisfy the noreturn contract.
Setup wrapper (source: CVE-2024-27397_mitigation/exploit.c L414-431):
Implement setup_cpu_entry_area() as follows. Call fork(); the parent returns immediately. The child: initializes the payload struct with kbase + gadget_offset in the appropriate slots; calls sched_setaffinity() to pin itself to HELPER_CPU; installs signal handlers for SIGFPE, SIGTRAP, and SIGSEGV; calls setsid() (required — detaches from terminal before fault); then calls write_cpu_entry_area() with the payload pointer.
Gen-2a uses a single eval pointer in the r15 slot. Gen-2b uses a three-slot nft pivot with a stack pivot gadget in r14 and a return gadget in r13.
Gen-3a uses a Qdisc_ops union with a two-stage COP chain. Gen-3b uses a raw
array layout with a ud2 instruction as the fault trigger instead of divq.
Five more Gen-2a CVEs (52620, 52924, 52925, 0193, 57947) and five more Gen-1 CVEs (3776 x2, 4206, 4207, 4208) use identical code patterns.
kbase + offset). If setup_cpu_entry_area() is called before kbase is
known, the payload will contain garbage.sleep(1) after setup_cpu_entry_area().Read references/theory.md for the kernel internals: how CEA is laid out,
why the address is fixed, how PUSH_AND_CLEAR_REGS writes the payload, and
the CVE-2023-0597 timeline.
Read references/implementation-guide.md for step-by-step integration,
variant selection, Gen-3a Qdisc_ops fill order, and a debugging checklist.
Read references/bibliography.md for exact source file line numbers,
kernel commit references, and gadget patterns reused across CVEs.