Two more IDEExtendedTaintAnalysis correctness fixes
Hi Fabian,
we found two more problems while debugging the obstacle/polyline activity from
the previous follow-up. One is a false positive at call boundaries, the other a
false negative after clang folds a source-dependent assignment into constant
stores. Both fixes are on the same branch:
https://gitlab.com/exida-oss/stig/phasar/-/tree/stig-patches-2026.06
We test the production combination we use, ide-xtaint with union-find.
13. Call flow crosses between disjoint actual-argument subobjects
This showed up as QM fields from one member of an activity reaching (ASIL-B)
fields in a different member after a call such as:
FanToPolylines(settings, this->fan_, resource, this->polylines_);
The access paths for fan_ and polylines_ have the same %this base.
getCallFlowFunction uses equivalentExceptPointerArithmetics when it decides
whether to transfer a fact to a pointer/reference formal. For a short actual
path this can reduce the comparison to the base, so a fact belonging to fan_
is also accepted for the polylines_ argument. transferFlowFact then keeps
the relative field offset and maps it back into the wrong object on return.
The fix keeps the permissive access-path comparison, but adds an argument-local
bounds check when LLVM still gives us an exact object size. The size comes from
the result element type of a GEP or from a direct alloca. A source is transferred
only if it is in the half-open byte interval of that actual argument:
actual offset <= source offset < actual offset + object size
If the exact size or a common coordinate base is not available, the old
conservative behaviour is retained. We intentionally do not use
dereferenceable(N) as an upper bound because it guarantees at least N
accessible bytes; it does not say that the object ends there. The same check is
used for fixed and variadic arguments.
Fix:
https://gitlab.com/exida-oss/stig/phasar/-/commit/0a2a0abe33eee101d656cce891f5bd5023280713
Reproducer:
https://gitlab.com/exida-oss/stig/stig/-/blob/main/stig-gradle-plugin/src/integrationTest/resources/cases/call_flow_disjoint_subobjects.cpp
The reproducer has one real input-to-output copy as a positive control and two
sinks in an unrelated output field. Before the fix the positive control passed,
but both unrelated sinks were reported. After the fix only the real copy is
reported.
14. Optimized source-dependent assignments lose their dependency
The simplified source has this shape:
if (input.qualifier == kOk) {
output.qualifier = input.qualifier;
} else {
output.qualifier = kNotAvailable;
}
The input remains unknown at compile time and the branch still executes at
runtime. At -O2, however, clang knows that input.qualifier == kOk inside the
first branch and emits constant stores for both branches. The input still
determines which constant is stored, but the explicit value edge from its load
to the store is gone. The comparison and branch condition are tainted;
IDEExtendedTaintAnalysis previously did not carry that taint to the output
store and therefore missed the sink.
The fix derives direct control dependencies with LLVM's post-dominator tree.
For a conditional branch or switch it walks each successor up to the
controller's immediate post-dominator. An integer or floating-point constant
store in that region inherits taint only from the exact controlling condition.
Stores at the merge or after it are not included. The fact is generated at the
store so the normal strong update does not immediately remove it.
This is deliberately narrow. It does not add general implicit-flow tainting;
it covers scalar constant stores whose original value dependency is otherwise
lost through optimization.
Fix:
https://gitlab.com/exida-oss/stig/phasar/-/commit/4d248681c96e4a0c2d536ddbe9f9d1f725564fe8
Reproducer:
https://gitlab.com/exida-oss/stig/stig/-/blob/main/stig-gradle-plugin/src/integrationTest/resources/cases/control_dependent_constant_store.cpp
Before the fix the expected sink_qm flow was missing. The separate constant
store after the branch merge is the negative control and remained clean before
and after the fix.
Verification
Both regressions were run against the old analyzer first:
- the call-flow test failed with two extra sinks;
- the optimized-store test failed with one missing sink.
With the two fixes applied:
- both focused regressions pass;
- the complete STIG analyzer matrix has no new unexpected failures;
- the native analyzer builds with Clang 22.1.7 and LLVM 18.1.8;
- the original local and fresh BMW activities both report
9/9 expected
data/control-flow sinks and 0/9 cascading-failure sinks.
The Kotlin assertions used to drive the fixtures are here:
Regards,
Piotr
==
Piotr Serwa
Principal Safety Engineer
exida
Two more
IDEExtendedTaintAnalysiscorrectness fixesHi Fabian,
we found two more problems while debugging the obstacle/polyline activity from
the previous follow-up. One is a false positive at call boundaries, the other a
false negative after clang folds a source-dependent assignment into constant
stores. Both fixes are on the same branch:
https://gitlab.com/exida-oss/stig/phasar/-/tree/stig-patches-2026.06
We test the production combination we use,
ide-xtaintwithunion-find.13. Call flow crosses between disjoint actual-argument subobjects
This showed up as QM fields from one member of an activity reaching (ASIL-B)
fields in a different member after a call such as:
The access paths for
fan_andpolylines_have the same%thisbase.getCallFlowFunctionusesequivalentExceptPointerArithmeticswhen it decideswhether to transfer a fact to a pointer/reference formal. For a short actual
path this can reduce the comparison to the base, so a fact belonging to
fan_is also accepted for the
polylines_argument.transferFlowFactthen keepsthe relative field offset and maps it back into the wrong object on return.
The fix keeps the permissive access-path comparison, but adds an argument-local
bounds check when LLVM still gives us an exact object size. The size comes from
the result element type of a GEP or from a direct alloca. A source is transferred
only if it is in the half-open byte interval of that actual argument:
If the exact size or a common coordinate base is not available, the old
conservative behaviour is retained. We intentionally do not use
dereferenceable(N)as an upper bound because it guarantees at leastNaccessible bytes; it does not say that the object ends there. The same check is
used for fixed and variadic arguments.
Fix:
https://gitlab.com/exida-oss/stig/phasar/-/commit/0a2a0abe33eee101d656cce891f5bd5023280713
Reproducer:
https://gitlab.com/exida-oss/stig/stig/-/blob/main/stig-gradle-plugin/src/integrationTest/resources/cases/call_flow_disjoint_subobjects.cpp
The reproducer has one real input-to-output copy as a positive control and two
sinks in an unrelated output field. Before the fix the positive control passed,
but both unrelated sinks were reported. After the fix only the real copy is
reported.
14. Optimized source-dependent assignments lose their dependency
The simplified source has this shape:
The input remains unknown at compile time and the branch still executes at
runtime. At
-O2, however, clang knows thatinput.qualifier == kOkinside thefirst branch and emits constant stores for both branches. The input still
determines which constant is stored, but the explicit value edge from its load
to the store is gone. The comparison and branch condition are tainted;
IDEExtendedTaintAnalysispreviously did not carry that taint to the outputstore and therefore missed the sink.
The fix derives direct control dependencies with LLVM's post-dominator tree.
For a conditional branch or switch it walks each successor up to the
controller's immediate post-dominator. An integer or floating-point constant
store in that region inherits taint only from the exact controlling condition.
Stores at the merge or after it are not included. The fact is generated at the
store so the normal strong update does not immediately remove it.
This is deliberately narrow. It does not add general implicit-flow tainting;
it covers scalar constant stores whose original value dependency is otherwise
lost through optimization.
Fix:
https://gitlab.com/exida-oss/stig/phasar/-/commit/4d248681c96e4a0c2d536ddbe9f9d1f725564fe8
Reproducer:
https://gitlab.com/exida-oss/stig/stig/-/blob/main/stig-gradle-plugin/src/integrationTest/resources/cases/control_dependent_constant_store.cpp
Before the fix the expected
sink_qmflow was missing. The separate constantstore after the branch merge is the negative control and remained clean before
and after the fix.
Verification
Both regressions were run against the old analyzer first:
With the two fixes applied:
9/9expecteddata/control-flow sinks and
0/9cascading-failure sinks.The Kotlin assertions used to drive the fixtures are here:
Regards,
Piotr
==
Piotr Serwa
Principal Safety Engineer
exida