Skip to content

Fix divide-before-multiply false positive when the divided variable is reassigned#3040

Open
MarkLee131 wants to merge 2 commits into
crytic:masterfrom
MarkLee131:fix/divide-before-multiply-reassignment-kill
Open

Fix divide-before-multiply false positive when the divided variable is reassigned#3040
MarkLee131 wants to merge 2 commits into
crytic:masterfrom
MarkLee131:fix/divide-before-multiply-reassignment-kill

Conversation

@MarkLee131

Copy link
Copy Markdown

Fixes #3039:

divide-before-multiply was flagging multiplications whose operand had been previously assigned a division result but was then fully overwritten with an unrelated value.

Root Cause

_explore in slither/detectors/statements/divide_before_multiply.py tracks the set of currently-division-tainted IR variables in a divisions: defaultdict[LVALUE, list[Node]] and walks the CFG with a worklist DFS. The Assignment handler only propagates taint:

if isinstance(ir, Assignment):
    if ir.rvalue in divisions:
        # propagate from rvalue to lvalue
        divisions[ir.lvalue] = divisions[ir.rvalue] + [node]

When ir.rvalue is a clean (non-division-tainted) value, divisions[ir.lvalue] is never updated. So a variable that was previously assigned a division result and then fully overwritten by a clean rvalue keeps its stale division taint, and the next multiplication using it fires.

This is a missing kill in the dataflow update.

Fix

Add an else branch that clears the stale taint when the rvalue is clean:

if isinstance(ir, Assignment):
    if ir.rvalue in divisions:
        # propagate (existing code)
        ...
    else:
        # rvalue is not division-tainted, so lvalue is being fully overwritten.
        # Clear any stale taint left over from a previous division assignment.
        divisions.pop(ir.lvalue, None)

Tests

Added tests/e2e/detectors/test_data/divide-before-multiply/0.8.20/divide_before_multiply_reassignment.sol. Three cases:

  • safeReassign — divided then reassigned to a clean value. Was an FP; silent after the fix.
  • safeChainedReassign — same shape via a temporary. Was an FP; silent after the fix.
  • genuineDivThenMul — the real divide-then-multiply pattern (tripwire). Still fires after the fix.

All four existing DivideBeforeMultiply fixtures (divide_before_multiply.sol × 0.4.25 / 0.5.16 / 0.6.11 / 0.7.6) pass with identical output, so the fix does not narrow the detector for any existing test case.

Scope

This fix narrowly addresses the missing kill on reassignment. It does not touch the over-approximation in the multiplication-check itself (the a / b * c pattern in fixed-point math, which is intentional precision loss at the developer's discretion) — those firings remain.

…assigned

The `_explore` worklist in `slither/detectors/statements/divide_before_multiply.py`
tracks the set of currently-division-tainted IR variables in a `divisions` dict.
The Assignment handler only propagates taint from rvalue to lvalue when the
rvalue is already in `divisions`. There is no else branch, so when the rvalue
is a clean (non-divided) value, the lvalue's previous taint is never cleared.

A variable that was previously assigned a division result and is then fully
overwritten with an unrelated value keeps its stale taint, and the next
multiplication using it fires:

  uint256 a = x / y;   // divisions[a] = [node15]
  a = z;               // rvalue clean; divisions[a] never cleared
  return a * 100;      // FP: `a` here is `z`, not x/y

Clear the stale taint in an `else` branch on Assignment so the dataflow kill
matches the join semantics of full overwrite.

Adds tests/e2e/detectors/test_data/divide-before-multiply/0.8.20/divide_before_multiply_reassignment.sol
covering two FP shapes (direct reassign, reassign through a temporary) plus a
tripwire `genuineDivThenMul` that must still fire. After this change the
snapshot contains only the tripwire; the two FPs are gone.

All four existing DivideBeforeMultiply fixtures still pass with identical
output.

The change does not touch the multiplication-check itself, so the
intentional-precision-loss firings (`a / b * c` in fixed-point math) are
unchanged.
…signment

Generated via:
  python tests/e2e/detectors/test_detectors.py --compile
  pytest -k DivideBeforeMultiply-0.8.20-divide_before_multiply_reassignment --insta update

All 5 DivideBeforeMultiply test cases pass with the fix applied.
@MarkLee131 MarkLee131 requested a review from smonicas as a code owner May 29, 2026 13:58
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.

[False-Positive]: divide-before-multiply fires when the divided variable is fully reassigned before the multiplication

1 participant