Skip to content

FLOWS_TO: handle-based writes are unmodeled for ALL lean languages, yielding a false NO_FLOW under flow_covered #1204

Description

@vitali87

Summary

The lean (non-Python) FLOWS_TO walk models direct call sinks only (IO_SINKS). It never consumes the handle-constructor / handle-method tables (IO_LEAN_HANDLE_CONSTRUCTORS, IO_LEAN_HANDLE_METHODS) — those feed only the io_access READS_FROM/WRITES_TO walk. As a result, a taint that reaches a resource through a handle method (os.Create(p).Write(x), io.open(p):write(x), File::create(p).write(x), fs.createWriteStream(p).write(x), …) emits no FLOWS_TO edge, while the module is still stamped flow_covered = True.

Under the three-verdict model that means a real ENV→FILE leak is certified as NO_FLOW (verified absence) instead of the honest UNKNOWN.

This is uniform across every lean language, not language-specific

Verified end-to-end with the real GraphUpdater (IO capture on):

Program flow_covered ENV→FILE FLOWS_TO
Go, direct os.WriteFile(p, []byte(s)) True ✅ emitted
Go, handle f,_ := os.Create(p); f.Write([]byte(s)) True ❌ none → false NO_FLOW
Lua, handle local f = io.open(p,"w"); f:write(s) True ❌ none → false NO_FLOW

Go (an established covered language) and Lua behave identically for the handle idiom. The boundary is direct-sink vs handle-method, and it is the same for Go, Rust, Java, JS/TS, C#, C, C++, and Lua.

Why this matters more for some languages

Languages with a direct one-shot file sink (Go os.WriteFile, Rust std::fs::write) have a modeled path for the common case; the gap only bites the handle idiom. Lua has no direct file-write sink — every Lua file write is handle-based — so Lua coverage is the starkest instance, though not a different bug.

Options

  1. Teach the FLOWS_TO lean walk to track handle bindings + handle-method sinks (reuse the io_access handle tables), so tainted handle.write(x) emits a FLOWS_TO edge to the handle's resource. Cross-language (also fixes Go/Rust/etc.).
  2. Or make flow_covered granular enough that a module containing an unmodeled handle write is reported as an UNKNOWN coverage gap rather than NO_FLOW.

Option 1 is the sound, parity-preserving fix.

Repro

Standalone script comparing the three programs above against the real GraphUpdater is available; it reproduces the table deterministically.

Surfaced by Greptile review on #1175 (Lua flow coverage); recorded here as the cross-cutting follow-up since it is not Lua-specific.

Metadata

Metadata

Assignees

No one assigned

    Labels

    advancedDifficult issuebugSomething isn't working correctly

    Projects

    Status
    Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions