Use-after-free is one of the oldest classes of memory corruption in C and C++ codebases, and it remains consistently present in the NVD's annual top vulnerability categories. The typical remediation story goes: SAST scan flags a potential UAF, developer verifies it is a real issue, patch is written, CVE is closed. That story works reasonably well for vulnerabilities in first-party code. It works much less well for vulnerabilities in the native extension dependencies that underlie many production services written in higher-level languages.
A Java or Python application may call a C library through JNI or ctypes. A Node.js service may use a native addon, while a Go program may link to C through cgo. In every case, memory management belongs to the native layer. The application developer did not write that C code, usually does not follow its commit history, and may not hear promptly when a UAF is found and fixed. The vulnerable code, meanwhile, continues executing in production.
Use-After-Free: Definition and Exploitability
A use-after-free happens when a program releases a heap allocation and later reads or writes the memory it released. The allocator returns that memory to its pool and may assign it to a later allocation in the same process or to another thread. If an attacker can dictate what occupies the freed region, they can influence the data the vulnerable code reads next or the address it invokes as a function pointer.
A small C example shows the pattern:
// CWE-416 example: simplified UAF pattern
typedef struct {
char name[64];
void (*process)(struct Request *); // function pointer
} Request;
Request *req = allocate_request(input);
free(req); // freed here
// ... later in the same function, or on another thread:
if (req->name[0] == '/') { // UAF read
req->process(req); // UAF function call -- controlled by attacker
// if attacker reclaimed the slot
}
How exploitable a UAF is depends on several conditions: timing, including whether an attacker can reliably trigger alloc-free-alloc; allocation-size alignment, including whether controlled data reaches the intended slot; and the role of the field accessed after freeing. A function pointer is valuable, while a display string is less so. SAST can recognize the structural pattern but cannot judge exploitability. Dynamic analysis can help, but only when an execution path reaches the vulnerable code under attacker-controlled conditions.
Why SAST and DAST Miss UAFs in Dependencies
For first-party C/C++ code, SAST products such as Coverity or CodeQL can detect many UAF patterns because you have the source. Native third-party dependencies are usually supplied as compiled binaries rather than source, sharply narrowing the available static-analysis options.
DAST tests the interfaces your application exposes, making it useful for application-layer logic flaws and injection bugs. But a UAF hidden in a native dependency's parser may require a precise input shape that the DAST fuzzer never creates. The flaw is present, yet the ordinary execution path never reaches the allocation-free-read sequence. DAST therefore misses it.
The missing piece is knowing whether a known-vulnerable native dependency is being exercised in production in a way that approaches its vulnerable path.
Kernel Observations During UAF Exploitation
A use-after-free in userspace memory is, by definition, a userspace event. eBPF cannot observe the heap operation directly without attaching to allocator internals, which would mean placing uprobes on libc's malloc/free and imposing substantial overhead on a production system. This is not a practical general-purpose method.
eBPF can observe the result of successful exploitation. If an attacker reclaims the freed allocation and redirects execution, the next events at the kernel boundary reveal the activity:
- mmap with PROT_EXEC. Shellcode execution often requires allocating executable memory. An
mmapcall with bothPROT_READ | PROT_EXECfrom a process that has no history of doing so is a signal. Combined with the preceding process state (a native library crash or memory consistency error), this is strong evidence of exploitation rather than normal JIT operation. - execve from an unexpected parent. If the exploitation goal is remote code execution, the attacker needs to spawn a shell or execute a payload. A shell spawn from a Java process, a Node.js process, or a Python interpreter is outside any legitimate behavioral model.
- Anomalous network connections. Many exploitation chains involve a callback to the attacker's C2 infrastructure. An outbound connection to an IP address outside the process's established network baseline, occurring within seconds of an unusual memory event, is the post-exploitation fingerprint.
Using eBPF to Detect UAF Exploitation
Oligo detects this class through event sequences rather than isolated alerts. The observation chain is:
- Detect a process crash or abnormal signal delivery (SIGSEGV or SIGBUS) in a process running known-vulnerable native code. This is instrumented via tracepoint on
signal_deliver. - Detect whether the process restarts (some applications trap crashes and restart automatically) and whether the restart is followed by anomalous memory allocation patterns (mmap with unusual protections).
- If exploitation succeeds without a crash (heap spray techniques can achieve this), watch for the post-exploitation behavioral signatures: shell spawn, unexpected outbound connection, or file writes to sensitive paths.
This method does not identify the UAF itself. It identifies UAF exploitation. That difference matters: the former calls for low-level heap tracing unsuitable for production, while the latter means watching system-call consequences at the boundary where eBPF operates.
The Limitation: Monitoring Is Not Prevention
Runtime observation of UAF exploitation provides detection, not prevention. When a native dependency is actively exploited, the alert occurs after the attempt has started. On some paths, that is still before the attacker reaches the objective, such as before spawning a shell or opening a C2 connection. On other paths, the alert may follow establishment of a foothold.
Runtime detection is not a replacement for patching vulnerable dependencies. When a library you use is disclosed with a CVE rooted in memory corruption, patching remains the right response. Runtime detection provides coverage between disclosure and patch deployment, a period that can last weeks or months for organizations managing hundreds of dependencies. It also exposes exploitation attempts in services whose vulnerable paths were thought unreachable, even though an attacker may understand what a running service actually exposes better than its defender.
Memory corruption will remain. Defense in depth is the right response: SCA identifies the vulnerability, runtime detects exploitation, and patching closes the window. Each addresses a different need.