fold/docs/internal/interprocedural-buffer-threading.md
trvon 0d63dbbd7e
research+feat: interprocedural buffer-bound threading — pre-pass architecture
- SafetyObserver::seedInterproceduralContext() called before function walks
- OOBObserver override scans all call sites for ConstantArrayType args
- Seeds param_buffer_sizes_ from any caller, regardless of decl order
- Research doc covers Inferbo/ARCHER/FunFrog approaches and fold's infrastructure
2026-05-29 23:49:56 -04:00

5 KiB

Interprocedural Buffer-Bound Threading — Research & Implementation Plan

Date: 2026-05-30 | Status: Research complete, implementation pending

Problem

fold misses the decode_base64 buffer overflow because the call chain is: main → validate_auth → decode_base64(basic_auth, decoded[0x100])

The array decoded[0x100] is local to validate_auth. Its 256-byte size must propagate to the output parameter inside decode_base64 for the OOB observer to catch output[output_index++] exceeding 256.

Root Cause (confirmed)

The walker processes functions in declaration order. decode_base64 is defined BEFORE validate_auth, so it's walked first with an empty param_buffer_sizes_ map. Then validate_auth walks, seedParamBufferSizes records param 1 → 256, but it's too late — decode_base64 was already processed.

Declaration order:  decode_base64 → validate_auth → main
Walk order:          decode_base64 (no buffer sizes yet)
                     validate_auth  (seeds param_buffer_sizes_)
                     main           (calls validate_auth)

Research Synthesis

Industry approaches

Tool Technique Key insight
Infer/Inferbo Compositional summaries with symbolic intervals Summary = {param → interval, ret → (offset, size)}. "Writers only provide intraprocedural analysis; Infer makes it compositional."
FunFrog Interpolation-based function summarization Uses Craig interpolation to create over-approximate summaries for BMC
ARCHER Path-sensitive, interprocedural symbolic analysis Bounds both variables and memory sizes symbolically across calls
SVF Value-flow graph with shortcut edges Summarizes interprocedural value flows with linear complexity

Inferbo's summary format (relevant pattern)

For `char* malloc_wrapper(int n)`:
  summary = { n |-> [s$6, s$7], ret |-> (offset: [0,0], size: [s$6, s$7]) }

This binds the parameter n to the return value's buffer size. In fold's terms: decode_base64 summary would have param_pre[1].kind = "bounds", expr = "arg1_size <= 256".

fold's existing infrastructure

Component File What it does
CalleeProperties cross_tu_cache.hpp:16 Tracks bounds_params per function
param_pre summary.hpp:18 Per-parameter preconditions (nonnull, nonzero, bounds)
seedParamBufferSizes oob_access.cpp:1315 Records buffer sizes from call-site array types
param_buffer_sizes_ oob_access.cpp:1358 Per-instance map, keyed on (FunctionDecl*, param_idx)
primeOOBAccessCrossTUCache oob_access.cpp:1352 Pre-pass that records callee properties
SummaryStore summary_store.cpp Shared across TUs, keyed on qualified name

Fix: Pre-Pass Architecture (M effort, ~150 lines)

Step 1: Add a buffer-size pre-pass to the security walker

Before any function is walked, scan all call sites in all function bodies in the TU. For each call like f(decoded) where decoded is ConstantArrayType with known size, record f, param_i → size in a shared (not per-function-walk) map.

Step 2: Add to OOBObserver initialization

Pass the pre-computed buffer size map to the constructor. The OOB observer reads from it instead of (or in addition to) the seedParamBufferSizes runtime seeding.

Step 3: Propagate through summaries

When computing a summary for validate_auth, if it calls decode_base64(basic_auth, decoded) where decoded has known size 256, and decode_base64's summary has a bounds precondition on param 1, then validate_auth's summary should ALSO carry a derived bounds constraint: decoded_size ≥ 256 (already known) → decode_base64_output_bound ≤ 256.

Implementation priority

  1. Pre-pass (S, highest ROI): Scan all call sites before walking any function. Populate a shared param_buffer_sizes_ accessible to all per-function OOB observers. This alone would catch decode_base64 because the seed happens before decode_base64 is walked.

  2. Summary threading (M): When computing a summary, propagate buffer bounds from callee summaries into the caller's summary as derived constraints.

  3. Transitive propagation (M-L): Full interprocedural (cross-TU) buffer size summaries — needed for real-world codebases where callers and callees are in different TUs.

References

  • Calcagno et al., "Moving Fast with Software Verification" (NFM 2015) — Infer's compositional analysis design
  • "Inferbo: Infer-based buffer overrun analyzer" (Facebook Research, 2017) — interval-based buffer size summaries
  • Sery et al., "FunFrog: Bounded Model Checking with Interpolation-Based Function Summarization" (HVC 2012)
  • Xie & Aiken, "ARCHER: using symbolic, path-sensitive analysis to detect memory access errors" (ESEC/FSE 2005)
  • Biere et al., "Bounded Model Checking" (Advances in Computers, 2003)
  • fold's summary infrastructure: src/symbolic/summary_encoder.cpp, include/fold/symbolic/summary.hpp