How eBPF Gives the System a Memory of What Happened
OS-Sanitizer’s value depends on how eBPF crosses a boundary that ordinary application testing often treats as background: the boundary between a program’s request and the kernel’s handling of it. This chapter focuses on the architecture that lets OS-Sanitizer collect facts from both sides, preserve them across separate events, and turn them into a report. Understanding who observes an event, who owns the accumulated state, and who makes the final judgment is the key to understanding what the prototype can—and cannot—claim.
The useful boundary is between intent and effect
An application expresses intent in user space. It calls a library function, opens a resource, maps memory, or asks the operating system to perform some other operation. The kernel then resolves that request, checks permissions, updates state, and produces an effect. The two sides are connected, but they do not contain the same information.
A user-space function may reveal what the application asked for. A kernel-side event may reveal what the system actually began to do: which resource was resolved, which permission check occurred, or which process state was involved. A defect can hide in the difference. The application may appear to make an ordinary request, while the kernel-side context shows that the request crosses an ownership boundary or relies on a fragile sequence of operations.
The Linux eBPF subsystem lets developers load small programs into the kernel at predefined attachment points through the bpf() system call. Those programs can inspect the context supplied by an event, query selected process or kernel state, read permitted user or kernel memory, maintain sandboxed data, and send information to a cooperative user-space process. The prototype uses that position to observe Linux applications without recompiling or modifying them.
The important idea is not that eBPF can see everything. It is that it can see related pieces of one behavior at more than one layer. OS-Sanitizer uses user-space probes at function entry and exit, kernel-function probes, and Linux security-module programs for security-relevant operations. Each attachment type supplies a different view. A user-space probe may capture the argument that began an operation. A kernel hook may see the operation’s actual path through the operating system. A security-module hook may supply permission context unavailable at the original call site.
Those views do not automatically become one explanation. They are observations with different timing and ownership. The analysis must decide which events belong together, how long their relationship remains valid, and what combination is meaningful. That is where the rest of the architecture begins.
A hook sees an event; a pass needs a story
A single event is often too thin to support a defect inference. Suppose a monitor sees an application open a file. That fact says little by itself. To assess whether the operation is suspicious, the monitor may need the path requested by the application, the components traversed while the kernel resolved it, the identities able to modify those components, the requested access mode, and the function that initiated the request.
Those facts arrive at different points. eBPF programs execute concurrently, atomically, and in isolation, so one program cannot simply assume that another program’s local variables are available. eBPF maps provide the shared, sandboxed state that connects them. A pass can place initial context in a map, let later programs enrich it, clear it when a new operation begins, and inspect it when the sequence reaches a decision point.
This gives OS-Sanitizer a recurring pattern:
- Collect. Identify the event that contains each needed fact and attach a small program there.
- Correlate. Store the facts in map-backed state associated with the relevant execution or operation.
- Decide. At a later event, apply a heuristic to the accumulated context and emit a report only when the pattern is suspicious enough.
The sequence is simple to describe but demanding to maintain. State must be associated with the right thread, process, or operation. It must be cleared when an old observation can no longer apply. Concurrent events must not be allowed to create a misleading mixture of contexts. A missing cleanup step can make a later report appear to describe one operation when it actually combines remnants of two.
This is why the architecture is more than a collection of hooks. The pass owns a small interpretation lifecycle. The kernel-side programs own the observations and the map updates. The map owns the temporary relationship among observations. The later decision point owns the heuristic check. The userspace side owns much of the filtering and presentation. No one component, viewed alone, is the whole detector.
The diagram also shows two different flows that should not be confused. The verifier controls whether an analysis program may be loaded and run. The application event supplies data to an approved program. The launcher then turns the collected data into something a person can inspect. Verifier approval is a safety gate on the observer; it is not a correctness verdict about the observed application.
The verifier makes in-kernel observation possible
Running analysis code inside the kernel creates a strict safety requirement. An ordinary application can often be terminated if it dereferences invalid memory or loops indefinitely. A kernel-resident program needs stronger guarantees because an error could corrupt kernel state or interfere with the system itself.
The Linux eBPF verifier addresses this requirement in two broad stages. It first checks control flow, including unreachable instructions and other structural problems. It then explores possible execution paths, simulating how each instruction changes register and stack state. The verifier does not merely inspect the program’s text. It tracks what the program could know at each point.
That tracked state includes whether a register has been initialized, whether it contains a scalar or a typed pointer, and whether a memory access is within bounds and correctly aligned. It checks that helper functions receive acceptable arguments and that references acquired during execution are released properly. A branch can refine the possible range of a value, while a previously accepted state can allow the verifier to prune an equivalent or safer path.
This explains the kinds of programs the verifier rejects. Reading an unwritten register is invalid because the value has no defined state. Reading stack memory before writing it is invalid for the same reason. Treating an integer as a pointer, accessing memory beyond a valid range, using a nullable result without checking it, or leaving a reference unreleased violates a different safety condition. These checks allow the kernel to run the program with bounded confidence about the analyzer itself.
The verifier’s knowledge is also a design constraint. An analysis pass must express its logic in a form the verifier can prove safe and within its complexity limits. The OS-Sanitizer paper identifies that bound as a hard theoretical limit, even though the authors did not struggle with it in their prototype. Writing the programs in Rust with the aya library helped make them easier to verify, but that engineering choice does not remove the underlying limit.
The distinction between safety and meaning is crucial. The verifier can establish that an eBPF program does not perform an invalid memory access under the states it explores. It cannot establish that a file operation violates an application guarantee, that a code smell is a vulnerability, or that a developer intended one privilege model rather than another. Those are application-level judgments made by OS-Sanitizer’s heuristics and, ultimately, by investigators.
There is also a version boundary to keep in view. The detailed verifier examples used here come from a Linux 6.2 documentation page, while the broader BPF documentation index is from a newer development documentation tree and describes the kernel-side material as still in progress. The concepts—state tracking, bounds, initialization, and path checking—are useful for the model, but exact behavior can vary with kernel version and program type.
From kernel evidence to an interpretable report
The kernel is the right place to observe many events, but it is not the right place to conduct the entire investigation. OS-Sanitizer’s launcher attaches the programs, receives their reports, and processes them through several stages. It deserializes the shared-buffer data, maps stack traces, performs filtering that cannot be done in eBPF alone, assigns or applies severity, and emits a standardized human-readable report. A second symbolization step can map binary offsets to source locations when the required debug information is available.
Each stage answers a different practical question. Deserialization makes the event usable outside the kernel. Stack-trace mapping connects a system event back to the process and call path that produced it. Filtering removes reports that fail local tests or fall below the user’s chosen severity. Reporting gives people a consistent description of what happened. Symbolization helps them find the relevant source rather than inspect raw binary offsets.
This division of labor also clarifies state ownership. The eBPF pass owns the facts it can collect at attachment points and the temporary relationships it maintains in maps. The launcher owns presentation, some local tests, and the user’s severity policy. The investigator owns the final interpretation. If symbolization is incomplete or a vendor binary lacks accessible source information, the report may still be real but harder to act on.
The paper’s prototype reflects this division in its implementation. The passes comprise roughly 2.8 thousand lines of Rust, and the launcher roughly 2.5 thousand. Those figures show that the usable system includes substantial coordination and reporting logic; they are not an inherent minimum for every eBPF analyzer. More important than the counts is the boundary they represent: collecting a signal and making it actionable are separate engineering tasks.
Why this architecture fits OS-Sanitizer’s question
OS-Sanitizer asks whether a running application’s behavior is suspicious in context, not merely whether an isolated function matches a source pattern. The eBPF architecture fits that question in three ways.
First, it can cover system behavior without requiring every target application to be rebuilt. That is particularly useful for dependencies, distribution-provided binaries, and programs whose maintainers cannot be changed for an experiment. The monitor can see behavior as it occurs in the environment where the software is actually used.
Second, it can connect user-space and kernel-space facts. A source-level view may show that a helper makes a file request; a kernel-side view can show the resolution and permission circumstances around that request. The combined evidence can reveal a mismatch between what the application assumes and what the system permits.
Third, map-backed state lets the pass reason across time. The meaning of one event can depend on what happened immediately before or after it. This is essential for contextual patterns such as an operation whose safety depends on a sequence of checks, traversals, or permissions rather than on one invalid instruction.
The same architecture creates responsibilities. More observations mean more state to clean up, correlate, filter, and explain. A system-wide monitor can produce repeated reports for repeated behavior. A hook at a frequently called user-space function can impose context-switch overhead. An analysis that needs source semantics or a language-level attachment point may be outside eBPF’s reach. The platform’s position gives OS-Sanitizer valuable evidence, but it does not make the evidence complete.
The final boundary is epistemic rather than mechanical. eBPF tells OS-Sanitizer what happened at selected points. Maps preserve a model of the surrounding event sequence. Heuristics decide which combinations deserve attention. The launcher makes them readable. None of those layers alone proves that the application is defective. The strength of a conclusion depends on how well the observed context supports it and what validation follows.
That gives us a complete working architecture: observe across the application–kernel boundary, preserve state across related events, keep the observer within verifier-enforced safety limits, and hand the resulting suspicion to userspace and human judgment. The next step is to combine those pieces into one evidence chain and ask, at each point, what is measured, what is inferred, and what remains to be checked.