Skip to content

feat(code-injection): report attributes chosen by input and then called - #257

Merged
SizzleUnrlsd merged 4 commits into
mainfrom
feat/attribute-call-sinks
Oct 15, 2026
Merged

SizzleUnrlsd merged 4 commits into
mainfrom
feat/attribute-call-sinks

Conversation

@SizzleUnrlsd

Copy link
Copy Markdown
Contributor

Part 2 of #240, second change: getattr(obj, name)(...). With #256 (import path), this completes #240.

Summary

Model:

  • Sink.requires_called_result holds the kinds that apply only when the call's result is itself called.
  • Merging keeps a kind conditional only when every declaration carrying it is, in either order. An unconditional declaration is never weakened, and an advisory sink added through ModelTable.extended does not widen a conditional kind.
  • Sink.applying(invoked) gives the kinds that apply to the call itself or to one invocation of its result.
  • getattr is a CODE sink on its name (argument 1), with selects, that requires its result to be called.

Summaries keep facts, independently of the models:

  • ExternalCall.invoked_at gives one record per invocation of an external call's result. location stays the selecting call.
  • The record key includes invoked_at, which is copied unchanged along helper chains.
  • Invocations are found by ir.defuse.invocations, through copies and phis.
  • Cache format 15.

The consuming engine filters:

  • In a function: unconditional kinds at the call; conditional kinds at each invocation, where the flow is anchored (TaintFlow.invoked_at). Checks between selection and invocation are therefore judged.
  • Through helpers: known() applies the same split per record.

Findings:

  • code-injection reports a hotspot titled Untrusted attribute call, on modules and behind prefixes too, with selected_line and invoked_line in the metadata.
  • A check proves the name only when it limits the value getattr received to constants. A check on a name rebound since, or on a derived value, proves nothing.
  • Documented limits: a result that is returned, stored or passed on before it is called; attrgetter/methodcaller/__getattribute__/vars(); checks inside helpers.

Verification

  • New tests/test_attribute_call_sinks.py and tests/test_sink_called_result.py: 33 tests, covering:

    • one finding per invocation, in a function and through one or two helpers;
    • converging selections;
    • rebound or derived names;
    • an unconditional sink whose result is called, reported once (eval(x)(), and an extra plugin declaring getattr unconditionally);
    • per-kind merging in both orders;
    • workers and a warm cache.

    No existing test changed apart from the luigi regression snapshot, in its own commit.

  • Mutation check: breaking the record key, the cache encoding, the merge or the kind split fails the new tests.

  • Full suite green, apart from the local test_packaging metadata issue. ruff check and mypy clean.

  • Regression: one new finding, a hotspot at luigi/server.py:101:

  • No getattr finding on mitmproxy or healthchecks; cache size +2.3% and +1.7%.

Closes #240

Part of #240: getattr(obj, name)(...), one finding per invocation.
luigi/server.py:101 calls getattr(self._scheduler, method) with the route's method; RPC_METHODS is a module-level registry, so the guard proves nothing and the flow is a hotspot.
@SizzleUnrlsd SizzleUnrlsd added the enhancement New feature or request label Oct 15, 2026
@SizzleUnrlsd SizzleUnrlsd self-assigned this Oct 15, 2026
…e calls of call results

A check proving the name where getattr selects holds past a join; a flow is refuted when its selection or its invocation cannot run, and a summary keeps an invocation only when both can. Calling the result of a call, or an item of it, now calls its __call__ instead of the symbol it comes from, so its arguments are not read as the original call's. Part of #240.
…elected in an earlier iteration

When the callable reaches its invocation across a loop's back edge, it was selected with an earlier instance of the name than the one a check at the invocation tests: only the checks where getattr selects prove it there. Part of #240.
@SizzleUnrlsd
SizzleUnrlsd merged commit 1f13cfc into main Oct 15, 2026
10 checks passed
@SizzleUnrlsd
SizzleUnrlsd deleted the feat/attribute-call-sinks branch October 15, 2026 06:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

code-injection: dynamic imports and runpy are not modelled as sinks

1 participant