soot.jimple.infoflow.river.AdditionalFlowCondition Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of soot-infoflow Show documentation
Show all versions of soot-infoflow Show documentation
Soot extending data flow tracking components for Java
The newest version!
package soot.jimple.infoflow.river;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import heros.solver.Pair;
import soot.*;
import soot.jimple.Stmt;
import soot.jimple.infoflow.results.*;
import soot.jimple.infoflow.sourcesSinks.definitions.SourceSinkCondition;
import soot.util.MultiMap;
/**
* A condition that checks additional data flow to see whether a source or sink
* is valid or not
*
* @author Steven Arzt
*
*/
public class AdditionalFlowCondition extends SourceSinkCondition {
private final Set classNamesOnPath;
private final Set signaturesOnPath;
private final Set excludedClassNames;
private Set methodsOnPath = null;
private Set classesOnPath = null;
private Set excludedClasses = null;
/**
* Create a new additional flow condition
*
* @param classNamesOnPath class names that have to be on the path
* @param signaturesOnPath signatures that have to be on the path
* @param excludedClassNames class names of primary sinks that should be filtered without context,
* e.g. ByteArrayOutputStream for OutputStream
*/
public AdditionalFlowCondition(Set classNamesOnPath, Set signaturesOnPath,
Set excludedClassNames) {
this.classNamesOnPath = classNamesOnPath;
this.signaturesOnPath = signaturesOnPath;
this.excludedClassNames = excludedClassNames;
}
@Override
public boolean evaluate(DataFlowResult result, InfoflowResults results) {
// If we have nothing to check, we accept everything
if (isEmpty())
return true;
MultiMap additionalResults = results.getAdditionalResults();
if (additionalResults == null || additionalResults.isEmpty())
return false;
// Get the connected flows
final Stmt sinkStmt = result.getSink().getStmt();
final Type baseType = result.getSource().getAccessPath().getBaseType();
// Shortcut: There won't be a path if the class is excluded anyway
ensureSootClassesOfExcludedClasses();
if (this.classMatches(baseType.toString(), excludedClasses))
return false;
// Because we injected the taint in the SecondaryFlowGenerator with a SecondarySinkDefinition,
// if there is a flow containing the sink, it is always also in the MultiMap.
Pair, Set> flows = getSignaturesAndClassNamesReachedFromSink(additionalResults, sinkStmt);
ensureSootMethodsOnPath();
boolean sigMatch = signaturesOnPath == null || signaturesOnPath.isEmpty()
|| flows.getO1().stream().anyMatch(this::signatureMatches);
ensureSootClassesOnPath();
boolean classMatch = classesOnPath == null || classesOnPath.isEmpty()
|| flows.getO2().stream().anyMatch(c -> this.classMatches(c, classesOnPath));
return sigMatch && classMatch;
}
private boolean signatureMatches(String sig) {
SootMethod sm = Scene.v().grabMethod(sig);
if (sm == null)
return false;
if (methodsOnPath.contains(sm))
return true;
for (SootClass ifc : sm.getDeclaringClass().getInterfaces()) {
SootMethod superMethod = ifc.getMethodUnsafe(sm.getSubSignature());
if (superMethod != null && methodsOnPath.contains(superMethod))
return true;
}
SootClass superClass = sm.getDeclaringClass().getSuperclassUnsafe();
while (superClass != null) {
SootMethod superMethod = superClass.getMethodUnsafe(sm.getSubSignature());
if (superMethod != null && methodsOnPath.contains(superMethod))
return true;
superClass = superClass.getSuperclassUnsafe();
}
return false;
}
private boolean classMatches(String sig, Set classes) {
SootClass sc = Scene.v().getSootClassUnsafe(sig);
if (sc == null)
return false;
if (classes.contains(sc))
return true;
for (SootClass ifc : sc.getInterfaces())
if (classes.contains(ifc))
return true;
SootClass superClass = sc.getSuperclassUnsafe();
while (superClass != null) {
if (classes.contains(superClass))
return true;
superClass = superClass.getSuperclassUnsafe();
}
return false;
}
@Override
public Set getReferencedMethods() {
ensureSootMethodsOnPath();
return methodsOnPath;
}
@Override
public Set getReferencedClasses() {
ensureSootClassesOnPath();
return classesOnPath;
}
@Override
public Set getExcludedClasses() {
ensureSootClassesOfExcludedClasses();
return excludedClasses;
}
/**
* Return the signatures that should be on the path
*
* @return unmodifiable set of signatures
*/
public Set getSignaturesOnPath() {
return Collections.unmodifiableSet(signaturesOnPath);
}
/**
* Return the class names that should be on the path
*
* @return unmodifiable set of class names
*/
public Set getClassNamesOnPath() {
return Collections.unmodifiableSet(classNamesOnPath);
}
/**
* Return the excluded class names
*
* @return unmodifiable set of excluded class names
*/
public Set getExcludedClassNames() {
return Collections.unmodifiableSet(excludedClassNames);
}
/**
* Ensures that the set of Soot methods on the data flow path has been
* initialized
*/
private void ensureSootMethodsOnPath() {
if (methodsOnPath == null) {
methodsOnPath = new HashSet<>();
if (signaturesOnPath != null && !signaturesOnPath.isEmpty()) {
for (String sig : signaturesOnPath) {
SootMethod sm = Scene.v().grabMethod(sig);
if (sm != null)
methodsOnPath.add(sm);
}
}
}
}
/**
* Ensures that the set of Soot classeson the data flow path has been
* initialized
*/
private void ensureSootClassesOnPath() {
if (classesOnPath == null)
classesOnPath = resolveSootClassesToSet(classNamesOnPath);
}
/**
* Ensures that the set of excluded classes for the condition
*/
private void ensureSootClassesOfExcludedClasses() {
if (excludedClasses == null)
excludedClasses = resolveSootClassesToSet(excludedClassNames);
}
private Set resolveSootClassesToSet(Set classNameSet) {
Set resolved = new HashSet<>();
if (classNameSet != null && !classNameSet.isEmpty()) {
for (String className : classNameSet) {
SootClass sc = Scene.v().getSootClassUnsafe(className);
if (sc != null)
resolved.add(sc);
}
}
return resolved;
}
/**
* Retrieves the signatures and classes that can be reached from the primary sink/secondary source
*
* @param additionalResults MultiMap containing the additional results
* @param primarySinkStmt Sink of interest
* @return A list of all callee signatures and a list of declaring classes on the path from the sink on
*/
private Pair, Set>
getSignaturesAndClassNamesReachedFromSink(MultiMap additionalResults,
Stmt primarySinkStmt) {
Set sigSet = new HashSet<>();
Set classSet = new HashSet<>();
for (ResultSinkInfo secondarySinkInfo : additionalResults.keySet()) {
for (ResultSourceInfo secondarySourceInfo : additionalResults.get(secondarySinkInfo)) {
// Match secondary source with primary sink of interest
if (secondarySourceInfo.getStmt() == primarySinkStmt
&& secondarySourceInfo.getDefinition() instanceof ConditionalSecondarySourceDefinition) {
if (secondarySourceInfo.getPath() == null) {
// Fall back if path reconstruction is not enabled
SootMethod callee = secondarySinkInfo.getStmt().getInvokeExpr().getMethod();
sigSet.add(callee.getSignature());
classSet.add(callee.getDeclaringClass().getName());
} else {
Stmt[] path = secondarySourceInfo.getPath();
for (Stmt stmt : path) {
if (stmt.containsInvokeExpr()) {
// Register all calls on the path
SootMethod callee = stmt.getInvokeExpr().getMethod();
sigSet.add(callee.getSignature());
classSet.add(callee.getDeclaringClass().getName());
}
}
}
}
}
}
return new Pair<>(sigSet, classSet);
}
/**
* Gets whether this flow condition is empty, i.e., has nothing to check for
*
* @return True if this flow condition is empty, false otherwise
*/
public boolean isEmpty() {
return (classNamesOnPath == null || classNamesOnPath.isEmpty())
&& (signaturesOnPath == null || signaturesOnPath.isEmpty());
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((classNamesOnPath == null) ? 0 : classNamesOnPath.hashCode());
result = prime * result + ((signaturesOnPath == null) ? 0 : signaturesOnPath.hashCode());
result = prime * result + ((excludedClassNames == null) ? 0 : excludedClassNames.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
AdditionalFlowCondition other = (AdditionalFlowCondition) obj;
if (classNamesOnPath == null) {
if (other.classNamesOnPath != null)
return false;
} else if (!classNamesOnPath.equals(other.classNamesOnPath))
return false;
if (signaturesOnPath == null) {
if (other.signaturesOnPath != null)
return false;
} else if (!signaturesOnPath.equals(other.signaturesOnPath))
return false;
if (excludedClassNames == null) {
if (other.excludedClassNames != null)
return false;
} else if (!excludedClassNames.equals(other.excludedClassNames))
return false;
return true;
}
@Override
public String toString() {
return "AdditionalFlowCondition: " +
"classNamesOnPath=" + classNamesOnPath +
", signaturesOnPath=" + signaturesOnPath +
", excludedClasses=" + excludedClassNames;
}
}