Skip to content

feat: decide advisory argument conditions at each call site - #119

Merged
SizzleUnrlsd merged 2 commits into
mainfrom
feat/argument-conditions
Sep 24, 2026
Merged

SizzleUnrlsd merged 2 commits into
mainfrom
feat/argument-conditions

Conversation

@SizzleUnrlsd

Copy link
Copy Markdown
Contributor

Closes #110.

Why

Advisory entry points carry argument conditions (yaml.load is affected by the FullLoader CVEs only when Loader is FullLoader; aiohttp's traversal only with follow_symlinks=True), but until now every condition was listed as pending review: yaml.load(f, Loader=yaml.SafeLoader) was reported reachable for every FullLoader CVE.

What

  • Arguments (coretrace_python.interprocedural): what each positional and keyword argument of a call denotes — a symbol's canonical name, a constant as Python writes it (True, None, '/static', bounded to 64 characters so log messages and queries stay out of the cache), or None when unknown — plus unpacked for *args/**kwargs. Computed once per call in CallGraphAnalysis; CallGraph.arguments_at() serves it.
  • Carried where decisions are made: CallSite.arguments (it replaces the argument counts, which nothing read), ExternalCall.arguments (kept through callees' summaries, so the deep sink call's arguments survive), TaintFlow.sink_arguments.
  • Condition.position and Condition.default, dumped and loaded in advisory files; a non-integer position or non-boolean default is rejected at load.
  • check_conditions(entry, arguments): each argument condition is met, contradicted (another known value, or absent without default) or undecided (a variable, unpacking). Semantic conditions are always pending.
  • reachable-vulnerability and the correlation skip calls that contradict a condition; findings gain conditions_met, and conditions_pending_review lists only what is left. vulnerable-dependency lists the contradicting calls in ruled_out (app:4 python.vulnlib.parse(mode=python.vulnlib.SAFE)) — the evidence that the imported requirement is not reached there — and now requires the call graph.
  • Cache format 6 → 7.

Tests

tests/test_argument_conditions.py (19): met by keyword, by position and by default; a constant met with nothing left pending; contradicted by another symbol, positionally, by None, by False, by absence — each ruled out with its reason and the requirement staying imported; undecided by a variable, **options, *rest; exploitable only through the meeting call; a flow through a callee of another module decided at its sink; the same decisions from the cache; file round trip; malformed position/default rejected; a long constant not recorded (checked to fail without the bound). The synthetic advisory of test_advisory_evidence.py states in its fields what its text already said (mode is the default, second argument); the cache codec test round-trips sites and external calls with arguments; the shipped manifest test gains interprocedural.callgraph for vulnerable-dependency.

951 passed; ruff and mypy clean. Usage guide documents the notation, position, default, conditions_met and ruled_out.

A call meeting an argument condition is reachable with the condition
met; one passing another known value is ruled out, and the vulnerable
requirement says which call and why; one passing a variable or unpacking
arguments leaves it pending review. The same holds for attacker input,
through a callee of another module, from the cache, and in advisory
files, which reject a malformed position or default. The synthetic
advisory's mode condition, whose text says the vulnerable mode is the
default, now says so in its fields.
Call sites record what each argument denotes - a symbol, a short
constant as Python writes it, or nothing known - once, in the call graph.
External calls carry it through summaries and taint flows carry it for
their sink call, so a flow through a callee of another module is decided
at the sink. An argument condition gains a position and a default. A call
meeting it is reported with conditions_met; a call passing another known
value does not reach the vulnerability, and the requirement's finding
lists it in ruled_out; a call passing a variable or unpacking arguments
leaves it pending review. CallSite.arguments replaces the argument counts
nothing read; the cache format is bumped.

Closes #110
@SizzleUnrlsd SizzleUnrlsd self-assigned this Sep 24, 2026
@SizzleUnrlsd
SizzleUnrlsd merged commit 69f5423 into main Sep 24, 2026
10 checks passed
@SizzleUnrlsd
SizzleUnrlsd deleted the feat/argument-conditions branch September 24, 2026 12:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Advisories: check argument conditions at the call site

1 participant