Home · ASPLOS 2024 · Compilers & Programming Languages

Kaleidoscope: Precise Invariant-Guided Pointer Analysis

Tapti Palit and Pedro Fonseca

Conference
ASPLOS 2024
Published
2024
Pages
561-576
Citations
2

Many security defenses need to know, for every pointer in a program, which objects or functions it can point to — but computing that purely by static analysis is hopelessly over-cautious, reporting far more targets than can ever really occur, which makes a defense like control-flow integrity so permissive it barely helps; this paper (Purdue) builds Kaleidoscope, which spots the few points where the analysis is about to lose precision, optimistically assumes the tighter answer holds, and cheaply checks that assumption at runtime — falling back to the safe over-approximation only if it is ever violated — shrinking the reported target sets by about 13 times while adding only about 5 percent overhead.

The argument in one glance

  1. Problem

    Security defenses need to know where pointers point, but the analysis is far too loose

    A whole family of security defenses depends on knowing, for every pointer in a program, the set of objects or functions it could point to — its points-to set. Control-flow integrity needs the possible targets of every function pointer so it can forbid an attacker from redirecting a call; software debloating needs the precise call graph; privilege separation needs to know which pointers reach privileged data. All of this rests on pointer analysis, a static technique that computes points-to sets without running the program. The catch is that static analysis cannot see runtime information, so to stay safe it over-approximates: the points-to set it reports is a superset of what can ever actually happen, often a wild overestimate. And the imprecision compounds — one over-broad answer feeds into the next, snowballing. On a lightweight SSL library, the leading analysis concludes that almost every indirect call could invoke almost every address-taken function, which makes the resulting control-flow-integrity policy so permissive it barely constrains an attacker at all.

  2. What people did

    Make the static analysis more precise, statically

    The traditional response is to sharpen the static analysis itself — add context sensitivity to distinguish different callers, field sensitivity to distinguish struct fields, flow and path sensitivity to track order and conditions — so the computed points-to sets are tighter without ever running the program.

  3. Why it fell short

    Full static precision is too expensive, and still cannot see runtime facts

    Cranking up static precision is enormously expensive: full context sensitivity, for instance, means re-analyzing functions separately for every calling context, which does not scale to systems of hundreds of thousands of lines. And even then, some facts are simply not available statically — they depend on values and paths that only exist at runtime — so a purely static analysis must remain conservative about them and stays imprecise where it matters most. The analysis is stuck between being too costly to compute and too coarse to be useful, and the compounding of imprecision means a few unavoidable over-approximations poison the whole result.

  4. This paper's idea

    Assume the precise answer optimistically, and check it at runtime

    Kaleidoscope takes a different route: invariant-guided optimistic analysis. As it solves the pointer constraints, it spots the specific derivations about to blow up precision and postulates a few likely invariants — statements like "this pointer never points to that object" — that it optimistically assumes hold. Assuming them arrests the snowball and yields far tighter points-to sets. To stay sound, it also produces a conservative fallback, instruments the program to monitor each invariant cheaply at runtime, and if one is ever violated it securely switches to the fallback. The result is precision close to the optimistic case, with a safety net that preserves soundness — about 13 times smaller target sets at roughly 5 percent overhead.

Why this problem is worth solving

The precision of this one analysis sets a ceiling on how strong a whole class of defenses can be.

Pointer analysis is a static program analysis that computes, for each pointer in a program, the set of memory objects or functions it may point to during any execution — its points-to set. Because it runs without executing the program, it cannot know exactly what will happen, so to be safe it must over-approximate: the reported points-to set includes every target that could possibly occur, and usually more. That conservatism is what makes it sound but imprecise. Many security mechanisms consume these sets directly: control-flow integrity turns the points-to set of a function pointer into the list of call targets it will permit, so a tighter set means a stricter, safer policy, while a bloated set means an attacker has more legal targets to redirect a call to. Precision here is therefore not an academic nicety; it is the difference between a defense that meaningfully constrains an attacker and one that permits nearly everything. The whole problem Kaleidoscope attacks is that static pointer analysis, applied to large real systems, is far too imprecise for the security uses that depend on it.

The reason a little imprecision becomes a lot is that it feeds on itself.

Pointer analysis solves a web of interdependent constraints: what one pointer can point to depends on what other pointers point to, which depends on the call graph, which depends on which function pointers can target which functions. Because of this interdependence, imprecision compounds — a single over-approximation early on makes a later derivation more over-approximate, which makes the next one worse still, so a few unavoidable coarse spots snowball into a globally loose result. For example, if the analysis is unsure which functions a function pointer can call, the call graph gains spurious edges, and along those spurious edges more pointer relationships are merged that never actually interact, inflating everything downstream. The practical lesson is that not all imprecision is equal: a small number of key derivations are responsible for triggering most of the cascade. If those specific spots could be kept precise, the snowball would be arrested and the overall result would tighten dramatically — which is exactly the leverage point Kaleidoscope aims for.

What people did before, and where it broke

  • Use a scalable but imprecise static analysis. Run a field-sensitive but context- and flow-insensitive analysis like the state-of-the-art framework, which scales to large code. Where it broke. It is too coarse: on real systems it reports points-to sets that are vast over-approximations, so a control-flow-integrity policy built on it permits almost every indirect call to reach almost every function.
  • Add full static context and flow sensitivity. Distinguish every calling context, field, and path to sharpen the points-to sets purely statically. Where it broke. The cost explodes — full context sensitivity re-analyzes functions per context and does not scale to hundreds of thousands of lines — and it still cannot recover facts that only exist at runtime.
  • Refine the analysis with heuristics or partial sensitivity. Apply cleverer static refinements to cut some imprecision without paying for full sensitivity. Where it broke. These help at the margins but remain purely static, so they cannot break the compounding at the spots driven by unavailable runtime information, and the snowball still dominates the result.
  • Just accept the loose policy. Deploy control-flow integrity with the imprecise target sets and hope the remaining constraint is worth something. Where it broke. A policy that lets nearly every indirect call invoke nearly every address-taken function gives an attacker so many legal targets that the defense provides little real protection.
  • The gap. No approach combined the scalability of an imprecise static analysis with precision at exactly the few derivations that drive the imprecision cascade, while remaining sound. What was missing was a way to be optimistic where it pays — assuming the tight answer at the key spots — and to guarantee safety if that optimism ever proves wrong.

How Kaleidoscope works

Optimistic pointer analysis with a sound runtime fallback

  1. new

  2. new

  3. new

  4. new

  5. new

The idea

Attack the imprecision where it snowballs, not everywhere. The defining realization is that pointer-analysis imprecision is not uniform: a small number of key derivations trigger a cascade that loosens everything downstream, so the payoff comes from keeping precisely those spots tight rather than sharpening the whole analysis uniformly. Kaleidoscope reads hints out of the constraint-solving process itself to find the derivations poised to explode, and concentrates its effort there. Recovering global precision by identifying and tightening the few high-leverage points where an analysis would otherwise lose track, instead of paying uniformly everywhere, is the same targeted-precision instinct the path-sensitive, alias-aware analysis work covered elsewhere on this site is built on, spending analysis effort exactly where distinguishing paths and aliases changes the answer and staying cheap elsewhere. By arresting the snowball at its sources, Kaleidoscope turns a handful of precise decisions into a large overall tightening of the points-to sets.

Be optimistic in the analysis, and validate the optimism at runtime. The core move is to stop insisting that static analysis prove everything on its own. At each high-leverage derivation, Kaleidoscope postulates a likely invariant — for instance that a particular pointer never points to a particular object — and optimistically assumes it holds, which yields the tight points-to sets a purely static analysis could never justify. Soundness is preserved not by proving the invariant but by checking it while the program runs: the program is instrumented so that if an invariant is ever violated, execution falls back to a conservative view that is always safe. Making an optimistic assumption to unlock the common-case-precise result, while inserting a runtime check that recovers correctness if the assumption ever fails, is the same speculate-then-validate discipline the speculative crash-consistency work covered elsewhere on this site relies on, assuming the favorable case to run efficiently and keeping a fallback that guarantees correctness when the assumption does not hold. Because the invariants are chosen to be both high-impact and few, the runtime monitoring stays cheap, and in practice the optimism was never wrong across the evaluated runs.

Compile both answers ahead of time and switch views securely at runtime. To make the fallback instantaneous and safe, Kaleidoscope does not recompute anything at runtime; it prepares both outcomes in advance. For every candidate invariant it explores both branches during analysis and emits two versions of the hardened code — the memory views — one built from the precise points-to sets and one from the conservative ones. For control-flow integrity these are the function-pointer validation checks placed before each indirect call, tight in the precise view and permissive-but-safe in the fallback. Monitors at the critical points watch the invariants, and on a violation the system securely switches which memory view governs execution, so the guarantee degrades gracefully rather than breaking. Turning a static analysis result directly into runtime enforcement — precomputed checks inserted before every indirect call that decide whether a control transfer is allowed — is the same enforcement strategy the runtime control-flow-and-data-flow work covered elsewhere on this site is built on, compiling a security policy into checks the running program applies at each dangerous operation.

Following an indirect call through Kaleidoscope

Tightening the CFI targets for one function pointer

A trace, from the paper, of how Kaleidoscope produces a tight, sound control-flow-integrity check for an indirect call in an SSL library.

Step 1 of 5

The baseline analysis reports a bloated target set

For an indirect call through a function pointer, the scalable static analysis, unable to distinguish calling contexts, merges relationships from many callsites and concludes the pointer could target a huge fraction of the address-taken functions — far more than can actually occur.

On the SSL library, the baseline finds almost every indirect callsite able to invoke almost every address-taken function, a nearly useless CFI policy.

Why it works

  • Targeting the snowball sources multiplies precision. Imprecision compounds, so a few key derivations poison the whole result → but keeping just those derivations precise arrests the cascade everywhere downstream → so a handful of optimistic assumptions shrink the average points-to set about 13 times over the state-of-the-art analysis.
  • Optimism recovers what static proof cannot. Some tightening depends on runtime facts a static analysis can never establish → but assuming a likely invariant lets the analysis use the tight answer anyway → so points-to sets reach a precision that no purely static, conservative analysis could justify.
  • Runtime validation keeps it sound. Assuming something unproven would normally be unsafe → but monitoring each invariant at runtime and falling back on violation guarantees the result is never wrong → so Kaleidoscope gains optimistic precision without giving up soundness.
  • Few, well-chosen invariants keep it cheap. Monitoring many conditions at runtime would be expensive → but selecting invariants that both cut a lot of imprecision and are few in number keeps the monitored set small → so the overhead stays around 5 percent while the precision gain is large.
  • Precomputing both views makes fallback instant and safe. Recomputing a conservative analysis at runtime would be impractical → but generating the precise and fallback memory views ahead of time lets a violation trigger an immediate, secure switch → so the defense degrades gracefully rather than failing when optimism is wrong.

What it costs (trade-offs and scope)

  • Precision depends on optimism that could, in principle, be violated. The tight points-to sets hold only while the likely invariants hold; if one is violated, that part of the program reverts to the conservative, imprecise policy. In the evaluation no invariant was violated, but the guarantee is graceful degradation to soundness, not that the precise policy always stays in force, and an adversary who could force a violation would push the affected checks back to the permissive fallback.
  • It requires instrumenting and monitoring the program. Kaleidoscope inserts runtime monitors at the critical points for each invariant and pays about 5 percent performance overhead; this is modest but nonzero, and it means the protected program carries added instrumentation and the machinery to switch memory views.
  • The choice of likely invariants is the crux and is heuristic. Effectiveness hinges on selecting invariants that both remove a lot of imprecision and are few enough to monitor cheaply; the paper derives these from hints in the constraint solving and a study of what drives imprecision, but the selection is a heuristic balance, and poorly chosen invariants would either help little or cost too much to monitor.
  • The fallback must be secure and correct to preserve the guarantee. Soundness rests on the runtime monitors correctly detecting every violation and on the secure switch to the fallback view; the security of the whole scheme depends on that mechanism being complete and tamper-resistant, which is an added surface compared with a purely static policy.
  • Evaluation is nine applications, for control-flow integrity. The reported 13.15 times average precision improvement and 5.45 percent overhead come from applying Kaleidoscope to forward-edge control-flow integrity on nine popular applications against one state-of-the-art baseline; the approach is presented as general to pointer-analysis use cases, but the demonstrated evidence is this security setting at this scale.

The evaluation

Computing points-to sets for security hardening

PropertyScalable imprecise analysisFull static context sensitivityKaleidoscope
Precision of points-to sets
Scalability to large systems
Uses runtime information
Soundness
CFI policy strength and cost
From the paper's evaluation applying Kaleidoscope to forward-edge control-flow integrity on nine applications against the state-of-the-art SVF pointer-analysis framework. Kaleidoscope reduces the average points-to set size by 13.15 times at a 5.45 percent performance overhead, with no likely invariant violated during the evaluated runs.Underlined values explain themselves when clicked.

Check yourself

Check your understanding

  1. 1. Why is static pointer analysis too imprecise for security defenses on real systems?

  2. 2. What is the core idea of invariant-guided optimistic analysis?

  3. 3. What are memory views, and how do they keep the system both fast and sound?

  4. 4. What did Kaleidoscope achieve, and what are the caveats?

Sources

The paper is “Kaleidoscope: Precise Invariant-Guided Pointer Analysis” by Tapti Palit and Pedro Fonseca (Purdue University), published at ASPLOS 2024 (Volume 3), DOI 10.1145/3620666.3651340.

This explainer is written from the full text of the authors’ copy of the paper. Everything above comes from the paper’s own sections: the introduction motivating pointer analysis for security uses (control-flow integrity, debloating, privilege separation) and its imprecision (points-to sets as supersets of the actual sets, with the MbedTLS example where the SVF framework concludes that 92 percent of indirect callsites can invoke 184 of 185 address-taken functions); the two insights (that imprecision compounds and snowballs so arresting it at key derivations yields large gains, and that the constraint-solving process itself gives hints for postulating likely invariants); the invariant-guided optimistic approach and its notion of Invariant-Guided Memory Views (exploring both the invariant-holds and invariant-fails paths to generate a precise and a conservative points-to collection, generating the two versions of hardened instructions such as the CFI function-pointer validation checks, inserting runtime monitors at critical program points, and securely switching to the fallback memory view on a violation to preserve soundness); the background on pointer-analysis precision dimensions (context, field, flow, and path sensitivity) and the SVF baseline; and the evaluation implementing fine-grained forward-edge control-flow integrity on nine applications, reporting a 13.15 times average precision improvement with a 5.45 percent performance overhead and no likely invariant violated during execution.

Related papers on Compilers & Programming Languages

See all 162 →