Skip to content

Commit d3bbf86

Browse files
committed
Fix a bug in FlowDroid with reused locals
1 parent 1a985d4 commit d3bbf86

6 files changed

Lines changed: 220 additions & 5 deletions

File tree

soot-infoflow/src/soot/jimple/infoflow/AbstractInfoflow.java

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,9 @@
5555
import soot.jimple.InvokeExpr;
5656
import soot.jimple.Jimple;
5757
import soot.jimple.Stmt;
58+
import soot.jimple.infoflow.FlowDroidLocalSplitter.SplittedLocal;
5859
import soot.jimple.infoflow.InfoflowConfiguration.AccessPathConfiguration;
60+
import soot.jimple.infoflow.InfoflowConfiguration.AliasingAlgorithm;
5961
import soot.jimple.infoflow.InfoflowConfiguration.CallgraphAlgorithm;
6062
import soot.jimple.infoflow.InfoflowConfiguration.CodeEliminationMode;
6163
import soot.jimple.infoflow.InfoflowConfiguration.DataFlowDirection;
@@ -512,16 +514,22 @@ protected void constructCallgraph() {
512514

513515
// To cope with broken APK files, we convert all classes that are still
514516
// dangling after resolution into phantoms
515-
for (SootClass sc : Scene.v().getClasses())
517+
for (SootClass sc : Scene.v().getClasses()) {
516518
if (sc.resolvingLevel() == SootClass.DANGLING) {
517519
sc.setResolvingLevel(SootClass.BODIES);
518520
sc.setPhantomClass();
519521
}
522+
}
520523

521524
// We explicitly select the packs we want to run for performance
522525
// reasons. Do not re-run the callgraph algorithm if the host
523526
// application already provides us with a CG.
524527
if (config.getCallgraphAlgorithm() != CallgraphAlgorithm.OnDemand && !Scene.v().hasCallGraph()) {
528+
if (config.getAliasingAlgorithm() == AliasingAlgorithm.PtsBased) {
529+
//we need to split here already for the PTS to work correctly
530+
splitAllBodies();
531+
}
532+
525533
PackManager.v().getPack("wjpp").apply();
526534
PackManager.v().getPack("cg").apply();
527535
}
@@ -911,8 +919,14 @@ protected void runAnalysis(final ISourceSinkManager sourcesSinks, final Set<Stri
911919
IInfoflowCFG iCfg = icfgFactory.buildBiDirICFG(config.getCallgraphAlgorithm(),
912920
config.getEnableExceptionTracking());
913921

914-
if (config.isTaintAnalysisEnabled())
915-
runTaintAnalysis(sourcesSinks, additionalSeeds, iCfg, performanceData);
922+
if (config.isTaintAnalysisEnabled()) {
923+
splitAllBodies();
924+
try {
925+
runTaintAnalysis(sourcesSinks, additionalSeeds, iCfg, performanceData);
926+
} finally {
927+
unsplitAllBodies();
928+
}
929+
}
916930

917931
// Gather performance data
918932
performanceData.setTotalRuntimeSeconds((int) Math.round((System.nanoTime() - beforeCallgraph) / 1E9));
@@ -939,6 +953,47 @@ protected void runAnalysis(final ISourceSinkManager sourcesSinks, final Set<Stri
939953
}
940954
}
941955

956+
private void unsplitAllBodies() {
957+
for (SootClass sc : Scene.v().getClasses()) {
958+
for (SootMethod m : sc.getMethods()) {
959+
if (m.hasActiveBody()) {
960+
//We could use the local packer here, but we know exactly what was being split
961+
//so we can be faster here
962+
Body body = m.getActiveBody();
963+
Iterator<ValueBox> it = body.getUseAndDefBoxesIterator();
964+
while (it.hasNext()) {
965+
ValueBox box = it.next();
966+
Value val = box.getValue();
967+
if (val instanceof SplittedLocal) {
968+
SplittedLocal l = (SplittedLocal) val;
969+
box.setValue(l.getOriginalLocal());
970+
}
971+
}
972+
Iterator<Local> lit = body.getLocals().iterator();
973+
while (lit.hasNext()) {
974+
if (lit.next() instanceof SplittedLocal) {
975+
lit.remove();
976+
}
977+
}
978+
}
979+
}
980+
}
981+
}
982+
983+
//With newer soot versions, locals are reused more often, which
984+
//can be a problem for FlowDroid. So, we split the locals prior to
985+
//running FlowDroid.
986+
protected void splitAllBodies() {
987+
FlowDroidLocalSplitter splitter = FlowDroidLocalSplitter.v();
988+
for (SootClass sc : new ArrayList<>(Scene.v().getApplicationClasses())) {
989+
for (SootMethod m : new ArrayList<>(sc.getMethods())) {
990+
if (m.isConcrete()) {
991+
splitter.transform(m.retrieveActiveBody());
992+
}
993+
}
994+
}
995+
}
996+
942997
/**
943998
* Since these simulations are not perfect, we should remove them afterwards
944999
*/
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package soot.jimple.infoflow;
2+
3+
import soot.Local;
4+
import soot.Singletons.Global;
5+
import soot.jimple.internal.JimpleLocal;
6+
import soot.toolkits.scalar.LocalSplitter;
7+
8+
/**
9+
* With more recent soot versions, locals are reused more often.
10+
* This can cause problems in FlowDroid (e.g. the overwriteParameter test case).
11+
* The simple solution: We split these locals beforehand
12+
* @author Marc Miltenberger
13+
*/
14+
public class FlowDroidLocalSplitter extends LocalSplitter {
15+
public static class SplittedLocal extends JimpleLocal {
16+
17+
private static final long serialVersionUID = 1L;
18+
private JimpleLocal originalLocal;
19+
20+
public SplittedLocal(JimpleLocal oldLocal) {
21+
super(null, oldLocal.getType());
22+
//do not intern the name again
23+
setName(oldLocal.getName());
24+
if (oldLocal.isUserDefinedLocal()) {
25+
setUserDefinedLocal();
26+
}
27+
28+
this.originalLocal = oldLocal;
29+
while (originalLocal instanceof SplittedLocal) {
30+
originalLocal = ((SplittedLocal) originalLocal).originalLocal;
31+
}
32+
}
33+
34+
public JimpleLocal getOriginalLocal() {
35+
return originalLocal;
36+
}
37+
38+
}
39+
40+
public FlowDroidLocalSplitter() {
41+
super((Global) null);
42+
}
43+
44+
@Override
45+
protected String getNewName(String name, int count) {
46+
//Reuse the old name
47+
return name;
48+
}
49+
50+
@Override
51+
protected Local createClonedLocal(Local oldLocal) {
52+
return new SplittedLocal((JimpleLocal) oldLocal);
53+
}
54+
55+
public static FlowDroidLocalSplitter v() {
56+
return new FlowDroidLocalSplitter();
57+
}
58+
59+
}

soot-infoflow/src/soot/jimple/infoflow/data/AccessPath.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -586,4 +586,14 @@ public static AccessPath getZeroAccessPath() {
586586
return zeroAccessPath;
587587
}
588588

589+
/**
590+
* Creates a new access path that is effectively the same, but based on another local as plain value
591+
* @param newValue the new value
592+
* @return the rebased access path
593+
*/
594+
public AccessPath rebaseTo(Local newValue) {
595+
return new AccessPath(newValue, baseType, fragments, taintSubFields, cutOffApproximation, arrayTaintType,
596+
canHaveImmutableAliases);
597+
}
598+
589599
}

soot-infoflow/src/soot/jimple/infoflow/results/AbstractResultSourceSinkInfo.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
package soot.jimple.infoflow.results;
22

3+
import soot.Local;
34
import soot.jimple.Stmt;
5+
import soot.jimple.infoflow.FlowDroidLocalSplitter.SplittedLocal;
46
import soot.jimple.infoflow.InfoflowConfiguration;
57
import soot.jimple.infoflow.data.AccessPath;
68
import soot.jimple.infoflow.sourcesSinks.definitions.ISourceSinkDefinition;
9+
import soot.jimple.internal.JimpleLocal;
710

811
/**
912
* Abstract base class for information on data flow results
@@ -35,7 +38,7 @@ public AbstractResultSourceSinkInfo(ISourceSinkDefinition definition, AccessPath
3538
assert accessPath != null;
3639

3740
this.definition = definition;
38-
this.accessPath = accessPath;
41+
this.accessPath = replaceAccessPath(accessPath);
3942
this.stmt = stmt;
4043
this.userData = userData;
4144
}
@@ -97,4 +100,15 @@ public boolean equals(Object o) {
97100
return true;
98101
}
99102

103+
protected AccessPath replaceAccessPath(AccessPath accessPath) {
104+
Local pv = accessPath.getPlainValue();
105+
if (pv instanceof SplittedLocal) {
106+
SplittedLocal s = (SplittedLocal) pv;
107+
JimpleLocal orig = s.getOriginalLocal();
108+
109+
AccessPath a = accessPath.rebaseTo(orig);
110+
return a;
111+
}
112+
return accessPath;
113+
}
100114
}

soot-infoflow/src/soot/jimple/infoflow/results/ResultSourceInfo.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ public ResultSourceInfo(ISourceSinkDefinition definition, AccessPath source, Stm
4646
this.pathCallSites = pathCallSites == null || pathCallSites.isEmpty() ? null
4747
: pathCallSites.toArray(new Stmt[pathCallSites.size()]);
4848
this.pathAgnosticResults = pathAgnosticResults;
49+
this.replaceSplitLocalsWithOriginals();
4950
}
5051

5152
public ResultSourceInfo(ISourceSinkDefinition definition, AccessPath source, Stmt context, Object userData,
@@ -56,6 +57,7 @@ public ResultSourceInfo(ISourceSinkDefinition definition, AccessPath source, Stm
5657
this.pathAPs = pathAPs;
5758
this.pathCallSites = pathCallSites;
5859
this.pathAgnosticResults = pathAgnosticResults;
60+
this.replaceSplitLocalsWithOriginals();
5961
}
6062

6163
public Stmt[] getPath() {
@@ -97,6 +99,14 @@ public int hashCode() {
9799
return result;
98100
}
99101

102+
private void replaceSplitLocalsWithOriginals() {
103+
if (pathAPs != null) {
104+
for (int i = 0; i < pathAPs.length; i++) {
105+
pathAPs[i] = replaceAccessPath(pathAPs[i]);
106+
}
107+
}
108+
}
109+
100110
@Override
101111
public boolean equals(Object obj) {
102112
if (this == obj)

soot-infoflow/test/soot/jimple/infoflow/test/junit/JUnitTests.java

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import java.io.File;
1919
import java.io.IOException;
2020
import java.util.ArrayList;
21+
import java.util.Iterator;
2122
import java.util.List;
2223

2324
import org.junit.Assume;
@@ -26,14 +27,27 @@
2627
import org.slf4j.Logger;
2728
import org.slf4j.LoggerFactory;
2829

30+
import heros.solver.Pair;
31+
import soot.Body;
32+
import soot.Local;
33+
import soot.Scene;
34+
import soot.SootClass;
35+
import soot.SootMethod;
36+
import soot.Value;
37+
import soot.ValueBox;
2938
import soot.jimple.infoflow.AbstractInfoflow;
3039
import soot.jimple.infoflow.BackwardsInfoflow;
40+
import soot.jimple.infoflow.FlowDroidLocalSplitter.SplittedLocal;
3141
import soot.jimple.infoflow.IInfoflow;
3242
import soot.jimple.infoflow.Infoflow;
3343
import soot.jimple.infoflow.config.ConfigForTest;
44+
import soot.jimple.infoflow.data.AccessPath;
3445
import soot.jimple.infoflow.results.InfoflowResults;
46+
import soot.jimple.infoflow.results.ResultSinkInfo;
47+
import soot.jimple.infoflow.results.ResultSourceInfo;
3548
import soot.jimple.infoflow.taintWrappers.EasyTaintWrapper;
3649
import soot.jimple.infoflow.test.base.AbstractJUnitTests;
50+
import soot.util.MultiMap;
3751

3852
/**
3953
* abstract super class of all test cases which handles initialization, keeps
@@ -115,8 +129,12 @@ public void resetSootAndStream() throws IOException {
115129
}
116130

117131
protected void checkInfoflow(IInfoflow infoflow, int resultCount) {
132+
checkCodeForNoSplitLocals();
133+
//check that there are no splitted locals left
118134
if (infoflow.isResultAvailable()) {
119135
InfoflowResults map = infoflow.getResults();
136+
checkInfoflowResultsForNoSplitLocals(map);
137+
120138
assertEquals(resultCount, map.size());
121139
assertTrue(map.containsSinkMethod(sink) || map.containsSinkMethod(sinkInt)
122140
|| map.containsSinkMethod(sinkBoolean) || map.containsSinkMethod(sinkDouble));
@@ -135,6 +153,55 @@ protected void checkInfoflow(IInfoflow infoflow, int resultCount) {
135153
}
136154
}
137155

156+
private void checkCodeForNoSplitLocals() {
157+
for (SootClass sc : Scene.v().getClasses()) {
158+
for (SootMethod m : sc.getMethods()) {
159+
if (m.hasActiveBody()) {
160+
Body b = m.getActiveBody();
161+
for (Local l : b.getLocals()) {
162+
if (l instanceof SplittedLocal) {
163+
fail(String.format("Found split local in %s: %s", m.getSignature(), l.getName()));
164+
}
165+
}
166+
167+
Iterator<ValueBox> it = b.getUseAndDefBoxesIterator();
168+
while (it.hasNext()) {
169+
ValueBox vb = it.next();
170+
Value v = vb.getValue();
171+
if (v instanceof SplittedLocal) {
172+
fail(String.format("Found split local in %s: %s", m.getSignature(), v));
173+
}
174+
}
175+
}
176+
}
177+
}
178+
}
179+
180+
private void checkInfoflowResultsForNoSplitLocals(InfoflowResults map) {
181+
MultiMap<ResultSinkInfo, ResultSourceInfo> res = map.getResults();
182+
if (res != null) {
183+
for (Pair<ResultSinkInfo, ResultSourceInfo> pair : res) {
184+
ResultSinkInfo sink = pair.getO1();
185+
ResultSourceInfo source = pair.getO2();
186+
checkAccessPathForSplitLocals(sink.getAccessPath());
187+
checkAccessPathForSplitLocals(source.getAccessPath());
188+
AccessPath[] app = source.getPathAccessPaths();
189+
if (app != null) {
190+
for (AccessPath i : app) {
191+
checkAccessPathForSplitLocals(i);
192+
}
193+
}
194+
}
195+
}
196+
}
197+
198+
private void checkAccessPathForSplitLocals(AccessPath ap) {
199+
Local l = ap.getPlainValue();
200+
if (l instanceof SplittedLocal) {
201+
fail("Split local found in " + ap);
202+
}
203+
}
204+
138205
protected void negativeCheckInfoflow(IInfoflow infoflow) {
139206
// If the result is available, it must be empty. Otherwise, it is
140207
// implicitly ok since we don't expect to find anything anyway.
@@ -166,7 +233,7 @@ protected IInfoflow initInfoflow(boolean useTaintWrapper) {
166233
}
167234

168235
}
169-
// result.getConfig().setLogSourcesAndSinks(true);
236+
// result.getConfig().setLogSourcesAndSinks(true);
170237

171238
return result;
172239
}

0 commit comments

Comments
 (0)