Debt Collector Series: §2 VMF Scan — Static Analysis Engine

§2 VMF Scan — Static Analysis Engine

Layer §1 of the Debt Collector Stack

Author: Shrikant Bhosale (@debtcollector21)


2.1 What It Is

VMF (Vulnerability Manifold Framework) is a multi-language static analysis engine that scans source code for 8-15 vulnerability patterns per language, scores each finding by geometric curvature, and projects them through 6 observation dimensions to collapse false positives.

The name “Manifold” is not decorative. Each finding is treated as a point on a high-dimensional manifold whose curvature determines its reality.


2.2 Architecture

Source Code → Language Adapter → Pattern Engine → Projection Engine → Findings
                    ↓                    ↓                   ↓
               Tree-sitter AST     8-15 regex/ast     6-dimensional
               + regex backend     patterns per lang   curvature scoring

2.2.1 Language Adapters

Each language has an adapter that handles parsing and pattern matching:

Language Patterns Backend Key Features
C/C++ 12 Tree-sitter + regex hybrid reinterpret_cast, null_deref, oob_access, two_phase_api, untrusted_memcpy, use_after_free
Java 12 Regex null_deref, oob_access, deserialization, sqli, xss, insecure_crypto
Go 12 Regex unsafe_pointer, nil_deref, slice_oob, sql_injection, goroutine_leak
Rust 12 Regex raw_pointer_deref, unsafe_block, unchecked_unwrap, transmute
Python 10 Regex eval_exec, sql_injection, command_injection, pickle_deserialize
JavaScript/TS 10 Regex prototype_pollution, xss, eval, insecure_random
Solidity 10 Regex reentrancy, tx_origin, delegatecall, unchecked_call, selfdestruct
PHP 8 Regex sqli, xss, file_include, command_injection

2.2.2 The C++ Hybrid Engine

The C++ adapter is the most sophisticated, using a two-phase approach:

Phase 1 — Tree-sitter AST:
– Parses C++ to concrete syntax tree
– Detects reinterpret_cast (exact node match — zero regex false positives)
– Detects memcpy/memmove (call expression matching)
– Detects free/delete (deallocation tracking)
– Extracts function_definition → compound_statement ranges for context
– Captures guard conditions (entire if-statement body marked as guarded)

Phase 2 — Regex fallback:
– Covers patterns that tree-sitter cannot easily express:
– Pointer arithmetic (ptr + offset, ptr[index])
– Integer overflow potential (size + len, count * elem_size)
– Null deref patterns (-> after unchecked allocation)
– Two-phase API detection (init/validate pattern)
– Format strings (printf without format string)
– Bounds access patterns (array subscript without bounds check)

Why hybrid? Pure regex has 66% false positive rate. Pure tree-sitter has 84% false positive rate (too few findings, misses real patterns). The hybrid achieves 81% collapse rate (precision) while covering both AST-detectable and regex-detectable patterns.

Adapter Points REAL COLLAPSED FP Rate Time
Pure regex 497 171 326 66% 18.7s
Pure tree-sitter 146 23 123 84% 0.11s
Hybrid 226 42 184 81% 0.12s

The hybrid is 156x faster than pure regex with lower FP rate.

2.2.3 Pattern Detection Logic

Each danger pattern follows the same internal structure:

class DangerPattern:
    name: str              # e.g., "raw_pointer_across_boundary"
    pattern_id: str        # e.g., "VMF-CPP-001"
    severity: str          # critical, high, medium, low, info
    cvss_vector: str       # e.g., "AV:L/AC:H/PR:L/UI:N/S:C/C:H/I:H/A:H"
    cwe: list[int]         # e.g., [822, 823]
    description: str       # Human-readable explanation
    detection: str         # Regex or AST pattern
    remediation: str       # How to fix

Example — raw_pointer_across_boundary:

{
    "name": "raw_pointer_across_boundary",
    "pattern_id": "VMF-CPP-001",
    "severity": "critical",
    "cvss_vector": "AV:L/AC:H/PR:L/UI:N/S:C/C:H/I:H/A:H",
    "cwe": [822, 823],
    "description": "Raw pointer derived from serialized data crosses trust boundary",
    "detection": "reinterpret_cast<.*\\s*\\*.*>",
    "remediation": "Use safe serialization (mojo::StructTraits, IPC::Message)"
}

2.3 The Projection Engine

Every raw finding passes through 5 geometric projection dimensions (see 01_MANIFESTO.md §1.5 for the mathematical definitions):

2.3.1 AST Projection

What it checks: Does the finding have valid structural context?

Implementation:

def project_ast(point):
    if not point.function_context:
        return 0.3  # weak — no function to anchor the pattern
    if tree_sitter_match(point.syntax_pattern, point.ast):
        return 1.0  # confirmed by AST
    return 0.7  # regex-only but in valid function context

2.3.2 Data Flow Projection

What it checks: Does attacker-controlled input reach the sink?

Implementation:

def project_data_flow(point, call_graph):
    traces = trace_taint(point.sink_variable, point.source_boundary)
    if not traces:
        return 0.3  # no data flow path — likely false positive
    shortest = min(len(t) for t in traces)
    return 1.0 / (1.0 + shortest * 0.1)  # shorter paths = higher confidence

2.3.3 Memory Layout Projection

What it checks: Is actual memory corruption possible?

Implementation:

def project_memory_layout(point):
    score = 0.0
    if point.has_allocation: score += 0.4
    if point.has_size_reference: score += 0.3
    if point.has_offset_computation: score += 0.3
    return score  # [0, 1]

2.3.4 Error State Projection

What it checks: Is this finding protected/unprotected by error handling?

Implementation:

def project_error_state(point):
    if point.in_try_block:
        if point.has_catch_handler:
            return 0.7  # handled — less likely real
        return 1.2  # try without catch = worse
    if point.error_return_checked:
        return 0.7
    if point.error_return_not_checked:
        return 1.3  # maximum information debt
    return 1.0  # neutral

2.3.5 Exploit Chain Projection

What it checks: Can this finding link to others?

Implementation:

def project_exploit_chain(point, attack_graph):
    pre = point.preconditions
    post = point.postconditions
    chainable = [n for n in attack_graph.nodes
                 if n.preconditions.issubset(post) or
                    post.issubset(n.postconditions)]
    if not chainable:
        return 0.3  # singleton — low chainability
    return min(1.0, len(chainable) * 0.2)  # more links = higher confidence

2.3.6 Collapse Decision

def should_collapse(kappa):
    """
    κ_total < 0.50  OR  any κ_d < 0.30  → COLLAPSE (false positive)
    """
    volume = prod(kappa.values())
    min_dim = min(kappa.values())
    return volume < 0.50 or min_dim < 0.30

2.4 Performance

Codebase Files Points Collapse REAL Time
[Vendor] (4 subsystems) 361 files 56,260 ~84% ~9,000 ~45s
Chrome Mojo 54 files 1,466 ~81% ~278 ~5s
Android Parcel.cpp 1 file (3,395 lines) 226 ~81% ~42 0.12s
Cash App (Java) 9,316 files ~500 ~80% ~99 ~30s

Scaling: Linear in file count. On a “Potato” rig (15GB RAM, 4 cores, no GPU), 1,000 files scans in ~30-60 seconds. Parallel mode (--parallel N) divides files across worker processes.


2.5 Validation Against Known Positives

Our validation dataset consists of 12 confirmed findings (all [Vendor]-assigned or ASAN-confirmed):

ID Pattern Target κ_total Collapse? Correct?
A01 capacity/size confusion Parcel.cpp L2603 0.92 No Yes
A02 raw_pointer_across_boundary Parcel.cpp objects[] 0.88 No Yes
A03 resize raw pointer Parcel.cpp resize path 0.85 No Yes
A04 objects[] bounds skip Parcel.cpp L2754 0.91 No Yes
A06 Information Debt (53 inst) AMS/NMS/UGMS 0.87 No Yes
VRP-004 DCHECK-only OOB channel.cc L718 0.82 No Yes
C01 close() no try/catch AndroidSqliteDriver 0.76 No Yes (Cash App)

Zero false negatives. All 12 confirmed findings survive projection. No known true positive has been collapsed.

We also tested against a set of 20 known false positives:

Pattern Reason for FP κ_total Collapse? Correct?
reinterpret_cast in type-safe wrapper No data flow from input 0.28 Yes Yes
null_deref on function parameter Parameter is always non-null 0.35 Yes Yes
memcpy in trusted initialization No attacker-controlled source 0.22 Yes Yes

100% known-FP recall. Every known false positive collapses in at least one dimension.


2.6 CLI Usage

# Basic scan
vmf scan src/ -o findings.json

# Parallel scan (4 workers)
vmf scan src/ -p 4 -o findings.json

# With projection engine
vmf scan src/ --projections -o findings_projected.json

# Language-specific
vmf scan src/ --lang cpp -o cpp_findings.json
vmf scan src/ --lang java -o java_findings.json

# No chains (skip chain linker, faster)
vmf scan src/ --no-chains

# Sequential (debug mode, no parallelism)
vmf scan src/ --sequential

2.6.1 Output Format

{
    "scan_id": "vmf_20260717_[Vendor]_parser",
    "target": "[Vendor]/src/parsing/",
    "timestamp": "2026-07-17T10:30:00Z",
    "summary": {
        "files_scanned": 89,
        "total_points": 32847,
        "collapsed": 27647,
        "real": 5200,
        "critical": 847,
        "high": 2103,
        "medium": 1450,
        "low": 800
    },
    "findings": [
        {
            "id": "VMF-20260717-001",
            "file": "scanner.cc",
            "line": 342,
            "pattern": "untrusted_memcpy",
            "pattern_id": "VMF-CPP-008",
            "curvature": {
                "ast": 0.95,
                "data_flow": 0.88,
                "memory_layout": 0.92,
                "error_state": 1.3,
                "exploit_chain": 0.85,
                "temporal": 1.0,
                "total": 0.87
            },
            "collapsed": false,
            "cvss": 8.3,
            "code": "memcpy(literal_buffer_, ...)",
            "chain_id": "CC-003"
        }
    ]
}

2.7 Limitations

  1. Language coverage gaps: Currently 12 languages. Missing: Swift, Kotlin, Ruby, Scala, Dart, Lua, Erlang, Elixir, Haskell. Adding a new language requires writing an adapter (typically 200-400 lines of Python).

  2. Tree-sitter dependency: The C++ hybrid engine requires the tree-sitter and tree-sitter-cpp Python packages. Without them, it falls back to pure regex (66% FP rate).

  3. No inter-procedural analysis: The current engine analyzes functions individually. Cross-function taint tracking and inter-procedural data flow analysis would require a call graph builder, which is planned for v3.

  4. No semantic analysis: The engine matches syntactic patterns but does not understand semantics. A reinterpret_cast in a type-safe wrapper is flagged the same as one in a dangerous serialization path — only the projection engine can distinguish them.

Leave a Comment