diff --git a/soot-infoflow-android/src/soot/jimple/infoflow/android/source/AndroidSourceSinkManager.java b/soot-infoflow-android/src/soot/jimple/infoflow/android/source/AndroidSourceSinkManager.java index 536c1fadd..4ecbe3282 100644 --- a/soot-infoflow-android/src/soot/jimple/infoflow/android/source/AndroidSourceSinkManager.java +++ b/soot-infoflow-android/src/soot/jimple/infoflow/android/source/AndroidSourceSinkManager.java @@ -440,6 +440,12 @@ protected Collection getSinkDefinitions(Stmt sCallSite, I final String subSig = callee.getSubSignature(); final SootClass sc = callee.getDeclaringClass(); + if (sc == null) { + logger.warn( + String.format("%s was not declared; was called at %s", callee.getSubSignature(), sCallSite)); + return sinkDefs; + } + // Do not consider ICC methods as sinks if only the base object is // tainted boolean isParamTainted = false; diff --git a/soot-infoflow-summaries/src/soot/jimple/infoflow/methodSummary/generator/SummaryInfoflow.java b/soot-infoflow-summaries/src/soot/jimple/infoflow/methodSummary/generator/SummaryInfoflow.java index e3b4c16e6..f9025ccb5 100644 --- a/soot-infoflow-summaries/src/soot/jimple/infoflow/methodSummary/generator/SummaryInfoflow.java +++ b/soot-infoflow-summaries/src/soot/jimple/infoflow/methodSummary/generator/SummaryInfoflow.java @@ -2,6 +2,8 @@ import java.util.Collection; +import soot.Local; +import soot.jimple.infoflow.FlowDroidLocalSplitter; import soot.jimple.infoflow.Infoflow; import soot.jimple.infoflow.InfoflowManager; import soot.jimple.infoflow.solver.IInfoflowSolver; @@ -35,10 +37,28 @@ public InfoflowManager getManager() { @Override protected void onTaintPropagationCompleted(IInfoflowSolver forwardSolver, IInfoflowSolver aliasSolver, - IInfoflowSolver backwardSolver, IInfoflowSolver backwardAliasSolver) { + IInfoflowSolver backwardSolver, IInfoflowSolver backwardAliasSolver) { cachedManager = this.manager; } + @Override + protected void unsplitAllBodies() { + //Since we are interested in abstractions, we must not unsplit. + //This is fine for our use case + } + + @Override + protected FlowDroidLocalSplitter getLocalSplitter() { + return new FlowDroidLocalSplitter() { + + @Override + protected Local createClonedLocal(Local oldLocal) { + //We want "normal" locals since we take deep looks into abstractions + return (Local) oldLocal.clone(); + } + }; + } + @Override protected void initializeSoot(String appPath, String libPath, Collection classes) { this.libPath = libPath; diff --git a/soot-infoflow/src/soot/jimple/infoflow/AbstractInfoflow.java b/soot-infoflow/src/soot/jimple/infoflow/AbstractInfoflow.java index 193c09958..09f2dba07 100644 --- a/soot-infoflow/src/soot/jimple/infoflow/AbstractInfoflow.java +++ b/soot-infoflow/src/soot/jimple/infoflow/AbstractInfoflow.java @@ -55,7 +55,9 @@ import soot.jimple.InvokeExpr; import soot.jimple.Jimple; import soot.jimple.Stmt; +import soot.jimple.infoflow.FlowDroidLocalSplitter.SplittedLocal; import soot.jimple.infoflow.InfoflowConfiguration.AccessPathConfiguration; +import soot.jimple.infoflow.InfoflowConfiguration.AliasingAlgorithm; import soot.jimple.infoflow.InfoflowConfiguration.CallgraphAlgorithm; import soot.jimple.infoflow.InfoflowConfiguration.CodeEliminationMode; import soot.jimple.infoflow.InfoflowConfiguration.DataFlowDirection; @@ -495,10 +497,11 @@ protected void constructCallgraph() { // Allow the ICC manager to change the Soot Scene before we continue if (ipcManager != null) ipcManager.updateJimpleForICC(); + if (config.getAliasingAlgorithm() == AliasingAlgorithm.PtsBased) { - // We might need to patch invokedynamic instructions - if (config.isPatchInvokeDynamicInstructions()) - patchDynamicInvokeInstructions(); + } + + patchCode(); // Run the preprocessors for (PreAnalysisHandler tr : preProcessors) @@ -512,11 +515,12 @@ protected void constructCallgraph() { // To cope with broken APK files, we convert all classes that are still // dangling after resolution into phantoms - for (SootClass sc : Scene.v().getClasses()) + for (SootClass sc : Scene.v().getClasses()) { if (sc.resolvingLevel() == SootClass.DANGLING) { sc.setResolvingLevel(SootClass.BODIES); sc.setPhantomClass(); } + } // We explicitly select the packs we want to run for performance // reasons. Do not re-run the callgraph algorithm if the host @@ -539,20 +543,20 @@ protected void constructCallgraph() { } /** - * Re-writes dynamic invocation instructions into traditional invcations + * Inserts patch-code logic */ - private void patchDynamicInvokeInstructions() { + private void patchCode() { for (SootClass sc : Scene.v().getClasses()) { for (SootMethod sm : sc.getMethods()) { if (sm.hasActiveBody()) { Body body = sm.getActiveBody(); - patchDynamicInvokeInstructions(body); + patchCode(body); } else if (!(sm.getSource() instanceof MethodSourceInjector) && sm.getSource() != null) { sm.setSource(new MethodSourceInjector(sm.getSource()) { @Override protected void onMethodSourceLoaded(SootMethod m, Body b) { - patchDynamicInvokeInstructions(b); + patchCode(b); } }); @@ -561,6 +565,17 @@ protected void onMethodSourceLoaded(SootMethod m, Body b) { } } + private void patchCode(Body body) { + if (config.isPatchInvokeDynamicInstructions()) { + patchDynamicInvokeInstructions(body); + } + getLocalSplitter().transform(body); + } + + protected FlowDroidLocalSplitter getLocalSplitter() { + return FlowDroidLocalSplitter.v(); + } + /** * Patches the dynamic invocation instructions in the given method body * @@ -911,8 +926,14 @@ protected void runAnalysis(final ISourceSinkManager sourcesSinks, final Set it = body.getUseAndDefBoxesIterator(); + while (it.hasNext()) { + ValueBox box = it.next(); + Value val = box.getValue(); + if (val instanceof SplittedLocal) { + SplittedLocal l = (SplittedLocal) val; + box.setValue(l.getOriginalLocal()); + } + } + Iterator lit = body.getLocals().iterator(); + while (lit.hasNext()) { + if (lit.next() instanceof SplittedLocal) { + lit.remove(); + } + } + } + } + } + } + + //With newer soot versions, locals are reused more often, which + //can be a problem for FlowDroid. So, we split the locals prior to + //running FlowDroid. + protected void splitAllBodies(Iterator it) { + FlowDroidLocalSplitter splitter = getLocalSplitter(); + while (it.hasNext()) { + MethodOrMethodContext mc = it.next(); + SootMethod m = mc.method(); + if (m.isConcrete() && m.getTag(SplittedTag.NAME) == null) { + m.addTag(SplittedTag.v()); + splitter.transform(m.retrieveActiveBody()); + } + } + } + /** * Since these simulations are not perfect, we should remove them afterwards */ diff --git a/soot-infoflow/src/soot/jimple/infoflow/FlowDroidLocalSplitter.java b/soot-infoflow/src/soot/jimple/infoflow/FlowDroidLocalSplitter.java new file mode 100644 index 000000000..d78ddeb1a --- /dev/null +++ b/soot-infoflow/src/soot/jimple/infoflow/FlowDroidLocalSplitter.java @@ -0,0 +1,70 @@ +package soot.jimple.infoflow; + +import java.util.Map; + +import soot.Body; +import soot.Local; +import soot.Singletons.Global; +import soot.jimple.internal.JimpleLocal; +import soot.toolkits.scalar.LocalSplitter; +import soot.toolkits.scalar.UnusedLocalEliminator; + +/** + * With more recent soot versions, locals are reused more often. This can cause + * problems in FlowDroid (e.g. the overwriteParameter test case). The simple + * solution: We split these locals beforehand + * + * @author Marc Miltenberger + */ +public class FlowDroidLocalSplitter extends LocalSplitter { + public static class SplittedLocal extends JimpleLocal { + + private static final long serialVersionUID = 1L; + private JimpleLocal originalLocal; + + public SplittedLocal(JimpleLocal oldLocal) { + super(null, oldLocal.getType()); + // do not intern the name again + setName(oldLocal.getName()); + if (oldLocal.isUserDefinedLocal()) { + setUserDefinedLocal(); + } + + this.originalLocal = oldLocal; + while (originalLocal instanceof SplittedLocal) { + originalLocal = ((SplittedLocal) originalLocal).originalLocal; + } + } + + public JimpleLocal getOriginalLocal() { + return originalLocal; + } + + } + + public FlowDroidLocalSplitter() { + super((Global) null); + } + + @Override + protected String getNewName(String name, int count) { + // Reuse the old name + return name; + } + + @Override + protected Local createClonedLocal(Local oldLocal) { + return new SplittedLocal((JimpleLocal) oldLocal); + } + + public static FlowDroidLocalSplitter v() { + return new FlowDroidLocalSplitter(); + } + + @Override + protected void internalTransform(Body body, String phaseName, Map options) { + super.internalTransform(body, phaseName, options); + UnusedLocalEliminator.v().transform(body); + } + +} diff --git a/soot-infoflow/src/soot/jimple/infoflow/SplittedTag.java b/soot-infoflow/src/soot/jimple/infoflow/SplittedTag.java new file mode 100644 index 000000000..084946a38 --- /dev/null +++ b/soot-infoflow/src/soot/jimple/infoflow/SplittedTag.java @@ -0,0 +1,19 @@ +package soot.jimple.infoflow; + +import soot.tagkit.Tag; + +public class SplittedTag implements Tag { + + public static final String NAME = "Splitted"; + private static final Tag INSTANCE = new SplittedTag(); + + @Override + public String getName() { + return NAME; + } + + public static Tag v() { + return INSTANCE; + } + +} diff --git a/soot-infoflow/src/soot/jimple/infoflow/data/AccessPath.java b/soot-infoflow/src/soot/jimple/infoflow/data/AccessPath.java index 0a897d622..637b52a39 100644 --- a/soot-infoflow/src/soot/jimple/infoflow/data/AccessPath.java +++ b/soot-infoflow/src/soot/jimple/infoflow/data/AccessPath.java @@ -586,4 +586,14 @@ public static AccessPath getZeroAccessPath() { return zeroAccessPath; } + /** + * Creates a new access path that is effectively the same, but based on another local as plain value + * @param newValue the new value + * @return the rebased access path + */ + public AccessPath rebaseTo(Local newValue) { + return new AccessPath(newValue, baseType, fragments, taintSubFields, cutOffApproximation, arrayTaintType, + canHaveImmutableAliases); + } + } diff --git a/soot-infoflow/src/soot/jimple/infoflow/data/pathBuilders/BatchPathBuilder.java b/soot-infoflow/src/soot/jimple/infoflow/data/pathBuilders/BatchPathBuilder.java index 538dac8eb..a0cbb74c9 100644 --- a/soot-infoflow/src/soot/jimple/infoflow/data/pathBuilders/BatchPathBuilder.java +++ b/soot-infoflow/src/soot/jimple/infoflow/data/pathBuilders/BatchPathBuilder.java @@ -38,6 +38,7 @@ public void computeTaintPaths(Set res) { int batchId = 1; long startTime = System.nanoTime(); long totalTime = manager.getConfig().getPathConfiguration().getPathReconstructionTotalTime(); + int completed = 0; while (resIt.hasNext()) { // checking if the execution time exceeds the configured totalTime and logging @@ -86,7 +87,8 @@ public void computeTaintPaths(Set res) { resultExecutor.reset(); } logger.info("Single batch has used " + (System.nanoTime() - beforeBatch) / 1E9 + " seconds"); - + completed += batch.size(); + reportCompletion(completed, res.size()); // If the analysis failed due to an OOM, it doesn't make sense to proceed with // the next batch and get into yet another OOM ISolverTerminationReason currentReason = innerBuilder.getTerminationReason(); @@ -106,6 +108,10 @@ public void computeTaintPaths(Set res) { } } + protected void reportCompletion(int completed, int totalTasks) { + + } + @Override public InfoflowResults getResults() { return innerBuilder.getResults(); diff --git a/soot-infoflow/src/soot/jimple/infoflow/results/AbstractResultSourceSinkInfo.java b/soot-infoflow/src/soot/jimple/infoflow/results/AbstractResultSourceSinkInfo.java index e96b1b124..269b75fb5 100644 --- a/soot-infoflow/src/soot/jimple/infoflow/results/AbstractResultSourceSinkInfo.java +++ b/soot-infoflow/src/soot/jimple/infoflow/results/AbstractResultSourceSinkInfo.java @@ -1,9 +1,12 @@ package soot.jimple.infoflow.results; +import soot.Local; import soot.jimple.Stmt; +import soot.jimple.infoflow.FlowDroidLocalSplitter.SplittedLocal; import soot.jimple.infoflow.InfoflowConfiguration; import soot.jimple.infoflow.data.AccessPath; import soot.jimple.infoflow.sourcesSinks.definitions.ISourceSinkDefinition; +import soot.jimple.internal.JimpleLocal; /** * Abstract base class for information on data flow results @@ -35,7 +38,7 @@ public AbstractResultSourceSinkInfo(ISourceSinkDefinition definition, AccessPath assert accessPath != null; this.definition = definition; - this.accessPath = accessPath; + this.accessPath = replaceAccessPath(accessPath); this.stmt = stmt; this.userData = userData; } @@ -97,4 +100,15 @@ public boolean equals(Object o) { return true; } + protected AccessPath replaceAccessPath(AccessPath accessPath) { + Local pv = accessPath.getPlainValue(); + if (pv instanceof SplittedLocal) { + SplittedLocal s = (SplittedLocal) pv; + JimpleLocal orig = s.getOriginalLocal(); + + AccessPath a = accessPath.rebaseTo(orig); + return a; + } + return accessPath; + } } diff --git a/soot-infoflow/src/soot/jimple/infoflow/results/ResultSourceInfo.java b/soot-infoflow/src/soot/jimple/infoflow/results/ResultSourceInfo.java index 1bc024300..08ebabfad 100644 --- a/soot-infoflow/src/soot/jimple/infoflow/results/ResultSourceInfo.java +++ b/soot-infoflow/src/soot/jimple/infoflow/results/ResultSourceInfo.java @@ -46,6 +46,7 @@ public ResultSourceInfo(ISourceSinkDefinition definition, AccessPath source, Stm this.pathCallSites = pathCallSites == null || pathCallSites.isEmpty() ? null : pathCallSites.toArray(new Stmt[pathCallSites.size()]); this.pathAgnosticResults = pathAgnosticResults; + this.replaceSplitLocalsWithOriginals(); } public ResultSourceInfo(ISourceSinkDefinition definition, AccessPath source, Stmt context, Object userData, @@ -56,6 +57,7 @@ public ResultSourceInfo(ISourceSinkDefinition definition, AccessPath source, Stm this.pathAPs = pathAPs; this.pathCallSites = pathCallSites; this.pathAgnosticResults = pathAgnosticResults; + this.replaceSplitLocalsWithOriginals(); } public Stmt[] getPath() { @@ -97,6 +99,14 @@ public int hashCode() { return result; } + private void replaceSplitLocalsWithOriginals() { + if (pathAPs != null) { + for (int i = 0; i < pathAPs.length; i++) { + pathAPs[i] = replaceAccessPath(pathAPs[i]); + } + } + } + @Override public boolean equals(Object obj) { if (this == obj) diff --git a/soot-infoflow/src/soot/jimple/infoflow/sourcesSinks/manager/BaseSourceSinkManager.java b/soot-infoflow/src/soot/jimple/infoflow/sourcesSinks/manager/BaseSourceSinkManager.java index 865c8fd38..df347680b 100644 --- a/soot-infoflow/src/soot/jimple/infoflow/sourcesSinks/manager/BaseSourceSinkManager.java +++ b/soot-infoflow/src/soot/jimple/infoflow/sourcesSinks/manager/BaseSourceSinkManager.java @@ -294,13 +294,15 @@ protected Collection getSinkDefinitions(Stmt sCallSite, I final String subSig = callee.getSubSignature(); - // Check whether we have any of the interfaces on the list - for (SootClass i : parentClassesAndInterfaces - .getUnchecked(sCallSite.getInvokeExpr().getMethod().getDeclaringClass())) { - if (i.declaresMethod(subSig)) { - Collection def = this.sinkMethods.get(i.getMethod(subSig)); - if (def.size() > 0) - return def; + SootClass decl = sCallSite.getInvokeExpr().getMethod().getDeclaringClass(); + if (decl != null) { + // Check whether we have any of the interfaces on the list + for (SootClass i : parentClassesAndInterfaces.getUnchecked(decl)) { + if (i.declaresMethod(subSig)) { + Collection def = this.sinkMethods.get(i.getMethod(subSig)); + if (def.size() > 0) + return def; + } } } diff --git a/soot-infoflow/test/soot/jimple/infoflow/test/junit/JUnitTests.java b/soot-infoflow/test/soot/jimple/infoflow/test/junit/JUnitTests.java index d0573aa26..4510a9cb7 100644 --- a/soot-infoflow/test/soot/jimple/infoflow/test/junit/JUnitTests.java +++ b/soot-infoflow/test/soot/jimple/infoflow/test/junit/JUnitTests.java @@ -18,6 +18,7 @@ import java.io.File; import java.io.IOException; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; import org.junit.Assume; @@ -26,14 +27,27 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import heros.solver.Pair; +import soot.Body; +import soot.Local; +import soot.Scene; +import soot.SootClass; +import soot.SootMethod; +import soot.Value; +import soot.ValueBox; import soot.jimple.infoflow.AbstractInfoflow; import soot.jimple.infoflow.BackwardsInfoflow; +import soot.jimple.infoflow.FlowDroidLocalSplitter.SplittedLocal; import soot.jimple.infoflow.IInfoflow; import soot.jimple.infoflow.Infoflow; import soot.jimple.infoflow.config.ConfigForTest; +import soot.jimple.infoflow.data.AccessPath; import soot.jimple.infoflow.results.InfoflowResults; +import soot.jimple.infoflow.results.ResultSinkInfo; +import soot.jimple.infoflow.results.ResultSourceInfo; import soot.jimple.infoflow.taintWrappers.EasyTaintWrapper; import soot.jimple.infoflow.test.base.AbstractJUnitTests; +import soot.util.MultiMap; /** * abstract super class of all test cases which handles initialization, keeps @@ -115,8 +129,12 @@ public void resetSootAndStream() throws IOException { } protected void checkInfoflow(IInfoflow infoflow, int resultCount) { + checkCodeForNoSplitLocals(); + //check that there are no splitted locals left if (infoflow.isResultAvailable()) { InfoflowResults map = infoflow.getResults(); + checkInfoflowResultsForNoSplitLocals(map); + assertEquals(resultCount, map.size()); assertTrue(map.containsSinkMethod(sink) || map.containsSinkMethod(sinkInt) || map.containsSinkMethod(sinkBoolean) || map.containsSinkMethod(sinkDouble)); @@ -135,6 +153,55 @@ protected void checkInfoflow(IInfoflow infoflow, int resultCount) { } } + private void checkCodeForNoSplitLocals() { + for (SootClass sc : Scene.v().getClasses()) { + for (SootMethod m : sc.getMethods()) { + if (m.hasActiveBody()) { + Body b = m.getActiveBody(); + for (Local l : b.getLocals()) { + if (l instanceof SplittedLocal) { + fail(String.format("Found split local in %s: %s", m.getSignature(), l.getName())); + } + } + + Iterator it = b.getUseAndDefBoxesIterator(); + while (it.hasNext()) { + ValueBox vb = it.next(); + Value v = vb.getValue(); + if (v instanceof SplittedLocal) { + fail(String.format("Found split local in %s: %s", m.getSignature(), v)); + } + } + } + } + } + } + + private void checkInfoflowResultsForNoSplitLocals(InfoflowResults map) { + MultiMap res = map.getResults(); + if (res != null) { + for (Pair pair : res) { + ResultSinkInfo sink = pair.getO1(); + ResultSourceInfo source = pair.getO2(); + checkAccessPathForSplitLocals(sink.getAccessPath()); + checkAccessPathForSplitLocals(source.getAccessPath()); + AccessPath[] app = source.getPathAccessPaths(); + if (app != null) { + for (AccessPath i : app) { + checkAccessPathForSplitLocals(i); + } + } + } + } + } + + private void checkAccessPathForSplitLocals(AccessPath ap) { + Local l = ap.getPlainValue(); + if (l instanceof SplittedLocal) { + fail("Split local found in " + ap); + } + } + protected void negativeCheckInfoflow(IInfoflow infoflow) { // If the result is available, it must be empty. Otherwise, it is // implicitly ok since we don't expect to find anything anyway. @@ -166,7 +233,7 @@ protected IInfoflow initInfoflow(boolean useTaintWrapper) { } } -// result.getConfig().setLogSourcesAndSinks(true); + // result.getConfig().setLogSourcesAndSinks(true); return result; }