soot.jimple.infoflow.problems.InfoflowProblem 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!
/*******************************************************************************
* Copyright (c) 2012 Secure Software Engineering Group at EC SPRIDE.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser Public License v2.1
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*
* Contributors: Christian Fritz, Steven Arzt, Siegfried Rasthofer, Eric
* Bodden, and others.
******************************************************************************/
package soot.jimple.infoflow.problems;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import heros.FlowFunction;
import heros.FlowFunctions;
import heros.flowfunc.KillAll;
import soot.BooleanType;
import soot.Local;
import soot.NullType;
import soot.PrimType;
import soot.RefType;
import soot.SootField;
import soot.SootMethod;
import soot.Type;
import soot.Unit;
import soot.Value;
import soot.jimple.ArrayRef;
import soot.jimple.AssignStmt;
import soot.jimple.CastExpr;
import soot.jimple.CmpExpr;
import soot.jimple.CmpgExpr;
import soot.jimple.CmplExpr;
import soot.jimple.ConditionExpr;
import soot.jimple.DefinitionStmt;
import soot.jimple.FieldRef;
import soot.jimple.InstanceFieldRef;
import soot.jimple.InstanceInvokeExpr;
import soot.jimple.InstanceOfExpr;
import soot.jimple.InvokeExpr;
import soot.jimple.LengthExpr;
import soot.jimple.NewArrayExpr;
import soot.jimple.ReturnStmt;
import soot.jimple.StaticFieldRef;
import soot.jimple.Stmt;
import soot.jimple.infoflow.InfoflowConfiguration.StaticFieldTrackingMode;
import soot.jimple.infoflow.InfoflowManager;
import soot.jimple.infoflow.aliasing.Aliasing;
import soot.jimple.infoflow.callmappers.CallerCalleeManager;
import soot.jimple.infoflow.callmappers.ICallerCalleeArgumentMapper;
import soot.jimple.infoflow.cfg.FlowDroidSinkStatement;
import soot.jimple.infoflow.cfg.FlowDroidSourceStatement;
import soot.jimple.infoflow.data.Abstraction;
import soot.jimple.infoflow.data.AccessPath;
import soot.jimple.infoflow.data.AccessPath.ArrayTaintType;
import soot.jimple.infoflow.data.ContainerContext;
import soot.jimple.infoflow.handlers.TaintPropagationHandler.FlowFunctionType;
import soot.jimple.infoflow.problems.rules.IPropagationRuleManagerFactory;
import soot.jimple.infoflow.solver.functions.SolverCallFlowFunction;
import soot.jimple.infoflow.solver.functions.SolverCallToReturnFlowFunction;
import soot.jimple.infoflow.solver.functions.SolverNormalFlowFunction;
import soot.jimple.infoflow.solver.functions.SolverReturnFlowFunction;
import soot.jimple.infoflow.typing.TypeUtils;
import soot.jimple.infoflow.util.BaseSelector;
import soot.jimple.infoflow.util.ByReferenceBoolean;
public class InfoflowProblem extends AbstractInfoflowProblem {
public InfoflowProblem(InfoflowManager manager, Abstraction zeroValue,
IPropagationRuleManagerFactory ruleManagerFactory) {
super(manager, zeroValue, ruleManagerFactory);
}
@Override
public FlowFunctions createFlowFunctionsFactory() {
return new FlowFunctions() {
/**
* Abstract base class for all normal flow functions. This is to share code that
* e.g. notifies the taint handlers between the various functions.
*
* @author Steven Arzt
*/
abstract class NotifyingNormalFlowFunction extends SolverNormalFlowFunction {
protected final Stmt stmt;
public NotifyingNormalFlowFunction(Stmt stmt) {
this.stmt = stmt;
}
@Override
public Set computeTargets(Abstraction d1, Abstraction source) {
// Notify the handler if we have one
if (taintPropagationHandler != null)
taintPropagationHandler.notifyFlowIn(stmt, source, manager,
FlowFunctionType.NormalFlowFunction);
// Compute the new abstractions
Set res = computeTargetsInternal(d1, source);
return notifyOutFlowHandlers(stmt, d1, source, res, FlowFunctionType.NormalFlowFunction);
}
public abstract Set computeTargetsInternal(Abstraction d1, Abstraction source);
}
/**
* Taints the left side of the given assignment
*
* @param assignStmt The source statement from which the taint originated
* @param targetValue The target value that shall now be tainted
* @param source The incoming taint abstraction from the source
* @param taintSet The taint set to which to add all newly produced taints
*/
private void addTaintViaStmt(final Abstraction d1, final AssignStmt assignStmt, Abstraction source,
Set taintSet, boolean cutFirstField, SootMethod method, Type targetType) {
final Value leftValue = assignStmt.getLeftOp();
final Value rightValue = assignStmt.getRightOp();
// Do not taint static fields unless the option is enabled
if (leftValue instanceof StaticFieldRef
&& manager.getConfig().getStaticFieldTrackingMode() == StaticFieldTrackingMode.None)
return;
Abstraction newAbs = null;
if (!source.getAccessPath().isEmpty()) {
// Special handling for array construction
if (leftValue instanceof ArrayRef && targetType != null) {
ArrayRef arrayRef = (ArrayRef) leftValue;
targetType = TypeUtils.buildArrayOrAddDimension(targetType, arrayRef.getType().getArrayType());
}
// If this is an unrealizable typecast, drop the abstraction
if (rightValue instanceof CastExpr) {
// For casts, we must update our typing information
CastExpr cast = (CastExpr) assignStmt.getRightOp();
if (!manager.getHierarchy().canStoreType(targetType, cast.getCastType()))
targetType = cast.getType();
}
// Comparisons don't propagate the type of the incoming tainted value
else if (rightValue instanceof CmpExpr || rightValue instanceof CmpgExpr
|| rightValue instanceof CmplExpr || rightValue instanceof ConditionExpr
|| rightValue instanceof LengthExpr)
targetType = null;
// Special type handling for certain operations
else if (rightValue instanceof InstanceOfExpr && manager.getConfig().getEnableInstanceOfTainting())
newAbs = source.deriveNewAbstraction(manager.getAccessPathFactory().createAccessPath(leftValue,
BooleanType.v(), true, ArrayTaintType.ContentsAndLength), assignStmt);
} else
// For implicit taints, we have no type information
assert targetType == null;
// Do we taint the contents of an array? If we do not
// differentiate, we do not set any special type.
ArrayTaintType arrayTaintType = source.getAccessPath().getArrayTaintType();
ContainerContext[] baseCtxt = null;
if (leftValue instanceof ArrayRef && manager.getConfig().getEnableArraySizeTainting()) {
arrayTaintType = ArrayTaintType.Contents;
baseCtxt = propagationRules.getArrayContextProvider().getContextForArrayRef((ArrayRef) leftValue,
assignStmt);
}
// also taint the target of the assignment
if (newAbs == null)
if (source.getAccessPath().isEmpty())
newAbs = source.deriveNewAbstraction(
manager.getAccessPathFactory().createAccessPath(leftValue, true), assignStmt, true);
else {
AccessPath ap = manager.getAccessPathFactory().copyWithNewValue(source.getAccessPath(),
leftValue, targetType, cutFirstField, true, arrayTaintType, baseCtxt);
newAbs = source.deriveNewAbstraction(ap, assignStmt);
}
if (newAbs != null) {
// Do we treat this taint specially, i.e., outside of IFDS?
if (leftValue instanceof StaticFieldRef && manager.getConfig()
.getStaticFieldTrackingMode() == StaticFieldTrackingMode.ContextFlowInsensitive) {
// We need to add the taint to our global taint state
manager.getGlobalTaintManager().addToGlobalTaintState(newAbs);
} else {
// Perform a normal IFDS-style propagation for the new taint
taintSet.add(newAbs);
final Aliasing aliasing = manager.getAliasing();
if (aliasing != null && aliasing.canHaveAliases(assignStmt, leftValue, newAbs)) {
aliasing.computeAliases(d1, assignStmt, leftValue, taintSet, method, newAbs);
}
}
}
}
/**
* Checks whether the given call has at least one valid target, i.e. a callee
* with a body.
*
* @param call The call site to check
* @return True if there is at least one callee implementation for the given
* call, otherwise false
*/
private boolean hasValidCallees(Unit call) {
Collection callees = interproceduralCFG().getCalleesOfCallAt(call);
for (SootMethod callee : callees)
if (callee.isConcrete())
return true;
return false;
}
private Set createNewTaintOnAssignment(final AssignStmt assignStmt, final Value[] rightVals,
Abstraction d1, final Abstraction newSource) {
final Value leftValue = assignStmt.getLeftOp();
final Value rightValue = assignStmt.getRightOp();
boolean addLeftValue = false;
// i = lengthof a. This is handled by a rule.
if (rightValue instanceof LengthExpr) {
return Collections.singleton(newSource);
}
// If we have an implicit flow, but the assigned
// local is never read outside the condition, we do
// not need to taint it.
boolean implicitTaint = newSource.getTopPostdominator() != null
&& newSource.getTopPostdominator().getUnit() != null;
implicitTaint |= newSource.getAccessPath().isEmpty();
// If we have a non-empty postdominator stack, we taint
// every assignment target
if (implicitTaint) {
// We can skip over all local assignments inside
// conditionally-called functions since they are not visible
// in the caller anyway
if ((d1 == null || d1.getAccessPath().isEmpty()) && !(leftValue instanceof FieldRef))
return Collections.singleton(newSource);
if (newSource.getAccessPath().isEmpty())
addLeftValue = true;
}
// If we have a = x with the taint "x" being inactive,
// we must not taint the left side. We can only taint
// the left side if the tainted value is some "x.y".
boolean aliasOverwritten = !addLeftValue && !newSource.isAbstractionActive()
&& Aliasing.baseMatchesStrict(rightValue, newSource) && rightValue.getType() instanceof RefType
&& !newSource.dependsOnCutAP();
// If we can't reason about aliases, there's little we can do here
final Aliasing aliasing = manager.getAliasing();
if (aliasing == null)
return null;
boolean cutFirstField = false;
AccessPath mappedAP = newSource.getAccessPath();
Type targetType = null;
if (!addLeftValue && !aliasOverwritten) {
for (Value rightVal : rightVals) {
if (rightVal instanceof FieldRef) {
// Get the field reference
FieldRef rightRef = (FieldRef) rightVal;
// If the right side references a NULL field, we
// kill the taint
if (rightRef instanceof InstanceFieldRef
&& ((InstanceFieldRef) rightRef).getBase().getType() instanceof NullType)
return null;
// Check for aliasing
mappedAP = aliasing.mayAlias(newSource.getAccessPath(), rightRef);
// check if static variable is tainted (same name,
// same class)
// y = X.f && X.f tainted --> y, X.f tainted
if (rightVal instanceof StaticFieldRef) {
if (manager.getConfig().getStaticFieldTrackingMode() != StaticFieldTrackingMode.None
&& mappedAP != null) {
addLeftValue = true;
cutFirstField = true;
}
}
// check for field references
// y = x.f && x tainted --> y, x tainted
// y = x.f && x.f tainted --> y, x tainted
else if (rightVal instanceof InstanceFieldRef) {
Local rightBase = (Local) ((InstanceFieldRef) rightRef).getBase();
Local sourceBase = newSource.getAccessPath().getPlainValue();
final SootField rightField = rightRef.getField();
// We need to compare the access path on the
// right side
// with the start of the given one
if (mappedAP != null) {
addLeftValue = true;
cutFirstField = (mappedAP.getFragmentCount() > 0
&& mappedAP.getFirstField() == rightField);
} else if (aliasing.mayAlias(rightBase, sourceBase)
&& newSource.getAccessPath().getFragmentCount() == 0
&& newSource.getAccessPath().getTaintSubFields()) {
addLeftValue = true;
targetType = rightField.getType();
if (mappedAP == null)
mappedAP = manager.getAccessPathFactory().createAccessPath(rightBase, true);
}
}
}
// indirect taint propagation:
// if rightvalue is local and source is instancefield of
// this local:
// y = x && x.f tainted --> y.f, x.f tainted
// y.g = x && x.f tainted --> y.g.f, x.f tainted
else if (rightVal instanceof Local && newSource.getAccessPath().isInstanceFieldRef()) {
Local base = newSource.getAccessPath().getPlainValue();
if (aliasing.mayAlias(rightVal, base)) {
addLeftValue = true;
targetType = newSource.getAccessPath().getBaseType();
}
}
// generic case, is true for Locals, ArrayRefs that are
// equal etc..
// y = x && x tainted --> y, x tainted
else if (aliasing.mayAlias(rightVal, newSource.getAccessPath().getPlainValue())) {
if (!(assignStmt.getRightOp() instanceof NewArrayExpr)) {
if (manager.getConfig().getEnableArraySizeTainting()
|| !(rightValue instanceof NewArrayExpr)) {
addLeftValue = true;
targetType = newSource.getAccessPath().getBaseType();
}
}
}
// One reason to taint the left side is enough
if (addLeftValue)
break;
}
}
// If we have nothing to add, we quit
if (!addLeftValue)
return null;
// Do not propagate non-active primitive taints
if (!newSource.isAbstractionActive() && (assignStmt.getLeftOp().getType() instanceof PrimType
|| (TypeUtils.isStringType(assignStmt.getLeftOp().getType())
&& !newSource.getAccessPath().getCanHaveImmutableAliases())))
return Collections.singleton(newSource);
Set res = new HashSet();
Abstraction targetAB = mappedAP.equals(newSource.getAccessPath()) ? newSource
: newSource.deriveNewAbstraction(mappedAP, null);
addTaintViaStmt(d1, assignStmt, targetAB, res, cutFirstField,
interproceduralCFG().getMethodOf(assignStmt), targetType);
res.add(newSource);
return res;
}
@Override
public FlowFunction getNormalFlowFunction(final Unit src, final Unit dest) {
// Get the call site
if (!(src instanceof Stmt))
return KillAll.v();
return new NotifyingNormalFlowFunction((Stmt) src) {
@Override
public Set computeTargetsInternal(Abstraction d1, Abstraction source) {
// Check whether we must activate a taint
final Abstraction newSource;
if (!source.isAbstractionActive() && src == source.getActivationUnit())
newSource = source.getActiveCopy();
else
newSource = source;
// Apply the propagation rules
ByReferenceBoolean killSource = new ByReferenceBoolean();
ByReferenceBoolean killAll = new ByReferenceBoolean();
Set res = propagationRules.applyNormalFlowFunction(d1, newSource, stmt,
(Stmt) dest, killSource, killAll);
if (killAll.value)
return Collections.emptySet();
// Propagate over an assignment
if (src instanceof AssignStmt) {
final AssignStmt assignStmt = (AssignStmt) src;
final Value right = assignStmt.getRightOp();
final Value[] rightVals = BaseSelector.selectBaseList(right, true);
// Create the new taints that may be created by this
// assignment
Set resAssign = createNewTaintOnAssignment(assignStmt, rightVals, d1,
newSource);
if (resAssign != null && !resAssign.isEmpty()) {
if (res != null) {
res.addAll(resAssign);
return res;
} else
res = resAssign;
}
}
// Return what we have so far
return res == null || res.isEmpty() ? Collections.emptySet() : res;
}
};
}
@Override
public FlowFunction getCallFlowFunction(final Unit src, final SootMethod dest) {
if (!dest.hasActiveBody()) {
logger.debug("Call skipped because target has no body: {} -> {}", src, dest);
return KillAll.v();
}
final Stmt stmt = (Stmt) src;
final InvokeExpr ie = (stmt != null && stmt.containsInvokeExpr()) ? stmt.getInvokeExpr() : null;
final Local[] paramLocals = dest.getActiveBody().getParameterLocals().toArray(new Local[0]);
// This is not cached by Soot, so accesses are more expensive
// than one might think
final Local thisLocal = dest.isStatic() ? null : dest.getActiveBody().getThisLocal();
// If we can't reason about aliases, there's little we can do here
final Aliasing aliasing = manager.getAliasing();
if (aliasing == null)
return KillAll.v();
return new SolverCallFlowFunction() {
@Override
public Set computeTargets(Abstraction d1, Abstraction source) {
Set res = computeTargetsInternal(d1, source);
if (res != null && !res.isEmpty() && d1 != null) {
for (Abstraction abs : res)
aliasing.getAliasingStrategy().injectCallingContext(abs, solver, dest, src, source, d1);
}
return notifyOutFlowHandlers(stmt, d1, source, res, FlowFunctionType.CallFlowFunction);
}
private Set computeTargetsInternal(Abstraction d1, Abstraction source) {
if (manager.getConfig().getStopAfterFirstFlow() && !results.isEmpty())
return null;
if (source == getZeroValue())
return null;
// Do not propagate into Soot library classes if that
// optimization is enabled
if (isExcluded(dest))
return null;
// Notify the handler if we have one
if (taintPropagationHandler != null)
taintPropagationHandler.notifyFlowIn(stmt, source, manager,
FlowFunctionType.CallFlowFunction);
ByReferenceBoolean killAll = new ByReferenceBoolean();
Set res = propagationRules.applyCallFlowFunction(d1, source, stmt, dest, killAll);
if (killAll.value)
return null;
// Map the source access path into the callee
Set resMapping = mapAccessPathToCallee(dest, stmt, ie, paramLocals, thisLocal,
source.getAccessPath());
if (resMapping == null)
return res;
// Translate the access paths into abstractions
Set resAbs = new HashSet(resMapping.size());
if (res != null && !res.isEmpty())
resAbs.addAll(res);
for (AccessPath ap : resMapping) {
if (ap != null) {
// If the variable is never read in the callee,
// there is no need to propagate it through
if (aliasing.getAliasingStrategy().isLazyAnalysis() || source.isImplicit()
|| interproceduralCFG().methodReadsValue(dest, ap.getPlainValue())) {
Abstraction newAbs = source.deriveNewAbstraction(ap, stmt);
if (newAbs != null)
resAbs.add(newAbs);
}
}
}
return resAbs;
}
};
}
@Override
public FlowFunction getReturnFlowFunction(final Unit callSite, final SootMethod callee,
final Unit exitStmt, final Unit retSite) {
// Get the call site
if (callSite != null && !(callSite instanceof Stmt))
return KillAll.v();
final Stmt iCallStmt = (Stmt) callSite;
final ReturnStmt returnStmt = (exitStmt instanceof ReturnStmt) ? (ReturnStmt) exitStmt : null;
final Local[] paramLocals = callee.getActiveBody().getParameterLocals().toArray(new Local[0]);
// If we can't reason about aliases, there's little we can do here
final Aliasing aliasing = manager.getAliasing();
if (aliasing == null)
return KillAll.v();
// This is not cached by Soot, so accesses are more expensive
// than one might think
final Local thisLocal = callee.isStatic() ? null : callee.getActiveBody().getThisLocal();
final ICallerCalleeArgumentMapper mapper = CallerCalleeManager.getMapper(manager, iCallStmt, callee);
final boolean isReflectiveCallSite = mapper != null ? mapper.isReflectiveMapper() : false;
return new SolverReturnFlowFunction() {
@Override
public Set computeTargets(Abstraction source, Abstraction d1,
Collection callerD1s) {
Set res = computeTargetsInternal(source, d1, callerD1s);
return notifyOutFlowHandlers(exitStmt, d1, source, res, FlowFunctionType.ReturnFlowFunction);
}
private Set computeTargetsInternal(Abstraction source, Abstraction calleeD1,
Collection callerD1s) {
if (manager.getConfig().getStopAfterFirstFlow() && !results.isEmpty())
return null;
if (source == getZeroValue())
return null;
// Notify the handler if we have one
if (taintPropagationHandler != null)
taintPropagationHandler.notifyFlowIn(exitStmt, source, manager,
FlowFunctionType.ReturnFlowFunction);
boolean callerD1sConditional = false;
for (Abstraction d1 : callerD1s) {
if (d1.getAccessPath().isEmpty()) {
callerD1sConditional = true;
break;
}
}
// Activate taint if necessary
Abstraction newSource = source;
if (!source.isAbstractionActive())
if (callSite != null)
if (callSite == source.getActivationUnit()
|| isCallSiteActivatingTaint(callSite, source.getActivationUnit()))
newSource = source.getActiveCopy();
// if abstraction is not active and activeStmt was in
// this method, it will not get activated = it can be
// removed:
if (!newSource.isAbstractionActive() && newSource.getActivationUnit() != null)
if (interproceduralCFG().getMethodOf(newSource.getActivationUnit()) == callee)
return null;
ByReferenceBoolean killAll = new ByReferenceBoolean();
Set res = propagationRules.applyReturnFlowFunction(callerD1s, calleeD1, newSource,
(Stmt) exitStmt, (Stmt) retSite, (Stmt) callSite, killAll);
if (killAll.value)
return null;
if (res == null)
res = new HashSet<>();
// If we have no caller, we have nowhere to propagate.
// This can happen when leaving the main method.
if (callSite == null)
return null;
// Do we need to retain all the taints?
if (aliasing.getAliasingStrategy().isLazyAnalysis()
&& Aliasing.canHaveAliases(newSource.getAccessPath()))
res.add(newSource);
// Static fields are handled in a rule
if (!newSource.getAccessPath().isStaticFieldRef() && !callee.isStaticInitializer()) {
// if we have a returnStmt we have to look at the
// returned value:
if (returnStmt != null && callSite instanceof DefinitionStmt) {
Value retLocal = returnStmt.getOp();
DefinitionStmt defnStmt = (DefinitionStmt) callSite;
Value leftOp = defnStmt.getLeftOp();
if (aliasing.mayAlias(retLocal, newSource.getAccessPath().getPlainValue())
&& !isExceptionHandler(retSite)) {
AccessPath ap = manager.getAccessPathFactory()
.copyWithNewValue(newSource.getAccessPath(), leftOp);
Abstraction abs = newSource.deriveNewAbstraction(ap, (Stmt) exitStmt);
if (abs != null) {
res.add(abs);
// Aliases of implicitly tainted variables must be mapped back into the caller's
// context on return when we leave the last implicitly-called method
if (aliasing.getAliasingStrategy().requiresAnalysisOnReturn())
for (Abstraction d1 : callerD1s)
aliasing.computeAliases(d1, iCallStmt, leftOp, res,
interproceduralCFG().getMethodOf(callSite), abs);
}
}
}
// Check parameters
Value sourceBase = newSource.getAccessPath().getPlainValue();
boolean parameterAliases = false;
{
Value originalCallArg = null;
for (int i = 0; i < callee.getParameterCount(); i++) {
int m = mapper.getCallerIndexOfCalleeParameter(i);
if (m == ICallerCalleeArgumentMapper.UNKNOWN)
continue;
if (m >= paramLocals.length)
continue;
// If this parameter is overwritten, we
// cannot propagate the "old" taint over.
// Return value propagation must always
// happen explicitly.
originalCallArg = iCallStmt.getInvokeExpr().getArg(i);
if (callSite instanceof DefinitionStmt && !isExceptionHandler(retSite)) {
DefinitionStmt defnStmt = (DefinitionStmt) callSite;
Value leftOp = defnStmt.getLeftOp();
if (originalCallArg == leftOp)
continue;
}
// Propagate over the parameter taint
// skip if the callee has more parameter than the iCallStmt.
// can happen by virtual edges added by soot (`virtualedges.xml`)
if (aliasing.mayAlias(paramLocals[m], sourceBase)) {
parameterAliases = true;
// If this is a constant parameter, we
// can safely ignore it
if (!AccessPath.canContainValue(originalCallArg))
continue;
if (!isReflectiveCallSite && !manager.getTypeUtils()
.checkCast(source.getAccessPath(), originalCallArg.getType()))
continue;
// If only the object itself, but no
// field is tainted, we can safely
// ignore it
if (!source.getAccessPath().getTaintSubFields())
continue;
// Primitive types and strings cannot
// have aliases and thus never need to
// be propagated back
if (source.getAccessPath().getBaseType() instanceof PrimType)
continue;
if (TypeUtils.isStringType(source.getAccessPath().getBaseType())
&& !source.getAccessPath().getCanHaveImmutableAliases())
continue;
// If the variable was overwritten
// somewehere in the callee, we assume
// it to overwritten on all paths (yeah,
// I know ...) Otherwise, we need SSA
// or lots of bookkeeping to avoid FPs
// (BytecodeTests.flowSensitivityTest1).
if (interproceduralCFG().methodWritesValue(callee, paramLocals[i]))
continue;
AccessPath ap = manager.getAccessPathFactory().copyWithNewValue(
newSource.getAccessPath(), originalCallArg,
isReflectiveCallSite ? null : newSource.getAccessPath().getBaseType(),
false);
Abstraction abs = newSource.deriveNewAbstraction(ap, (Stmt) exitStmt);
if (abs != null) {
res.add(abs);
for (Abstraction callerD1 : callerD1s)
manager.getAliasing().computeAliases(callerD1, iCallStmt,
originalCallArg, res,
interproceduralCFG().getMethodOf(callSite), abs);
}
}
}
}
// If this parameter is overwritten, we
// cannot propagate the "old" taint over. Return
// value propagation must always happen explicitly.
boolean thisAliases = false;
if (callSite instanceof DefinitionStmt && !isExceptionHandler(retSite)) {
DefinitionStmt defnStmt = (DefinitionStmt) callSite;
Value leftOp = defnStmt.getLeftOp();
if (thisLocal == leftOp)
thisAliases = true;
}
// check if it is not one of the params
// (then we have already fixed it)
if (!parameterAliases && !thisAliases && source.getAccessPath().getTaintSubFields()
&& aliasing.mayAlias(thisLocal, sourceBase)) {
// Type check
if (manager.getTypeUtils().checkCast(source.getAccessPath(), thisLocal.getType())) {
// Get the caller-side base local
// and create a new access path for it
Value callerBaseLocal = mapper.getCallerValueOfCalleeParameter(
iCallStmt.getInvokeExpr(), ICallerCalleeArgumentMapper.BASE_OBJECT);
if (callerBaseLocal != null) {
AccessPath ap = manager.getAccessPathFactory().copyWithNewValue(
newSource.getAccessPath(), callerBaseLocal,
isReflectiveCallSite ? null : newSource.getAccessPath().getBaseType(),
false);
Abstraction abs = newSource.deriveNewAbstraction(ap, (Stmt) exitStmt);
if (abs != null) {
res.add(abs);
if (!abs.equals(calleeD1))
for (Abstraction callerD1 : callerD1s)
manager.getAliasing().computeAliases(callerD1, iCallStmt,
callerBaseLocal, res,
interproceduralCFG().getMethodOf(iCallStmt), abs);
}
}
}
}
}
for (Abstraction abs : res) {
// Aliases of implicitly tainted variables must be
// mapped back into the caller's context on return
// when we leave the last implicitly-called method
if ((abs.isImplicit() && !callerD1sConditional)
|| aliasing.getAliasingStrategy().requiresAnalysisOnReturn()) {
for (Abstraction d1 : callerD1s) {
aliasing.computeAliases(d1, iCallStmt, null, res,
interproceduralCFG().getMethodOf(callSite), abs);
}
}
// Set the corresponding call site
if (abs != newSource) {
abs.setCorrespondingCallSite(iCallStmt);
}
}
return res;
}
};
}
@Override
public FlowFunction getCallToReturnFlowFunction(final Unit call, final Unit returnSite) {
// special treatment for native methods:
if (!(call instanceof Stmt))
return KillAll.v();
final Stmt iCallStmt = (Stmt) call;
final InvokeExpr invExpr = iCallStmt.getInvokeExpr();
// If we can't reason about aliases, there's little we can do here
final Aliasing aliasing = manager.getAliasing();
if (aliasing == null)
return KillAll.v();
final Value[] callArgs = new Value[invExpr.getArgCount()];
for (int i = 0; i < invExpr.getArgCount(); i++)
callArgs[i] = invExpr.getArg(i);
final boolean isSink = iCallStmt.hasTag(FlowDroidSinkStatement.TAG_NAME);
final boolean isSource = iCallStmt.hasTag(FlowDroidSourceStatement.TAG_NAME);
final SootMethod callee = invExpr.getMethod();
final boolean hasValidCallees = hasValidCallees(call);
return new SolverCallToReturnFlowFunction() {
@Override
public Set computeTargets(Abstraction d1, Abstraction source) {
Set res = computeTargetsInternal(d1, source);
return notifyOutFlowHandlers(call, d1, source, res, FlowFunctionType.CallToReturnFlowFunction);
}
private Set computeTargetsInternal(Abstraction d1, Abstraction source) {
if (manager.getConfig().getStopAfterFirstFlow() && !results.isEmpty())
return null;
// Notify the handler if we have one
if (taintPropagationHandler != null)
taintPropagationHandler.notifyFlowIn(call, source, manager,
FlowFunctionType.CallToReturnFlowFunction);
// check inactive elements:
final Abstraction newSource;
if (!source.isAbstractionActive() && (call == source.getActivationUnit()
|| isCallSiteActivatingTaint(call, source.getActivationUnit())))
newSource = source.getActiveCopy();
else
newSource = source;
ByReferenceBoolean killSource = new ByReferenceBoolean();
ByReferenceBoolean killAll = new ByReferenceBoolean();
Set res = propagationRules.applyCallToReturnFlowFunction(d1, newSource, iCallStmt,
killSource, killAll, true);
if (killAll.value)
return null;
boolean passOn = !killSource.value;
// Do not propagate zero abstractions
if (source == getZeroValue())
return res == null || res.isEmpty() ? Collections.emptySet() : res;
// Initialize the result set
if (res == null)
res = new HashSet<>();
if (newSource.getTopPostdominator() != null
&& newSource.getTopPostdominator().getUnit() == null)
return Collections.singleton(newSource);
// Static taints must always go through the callee
if (newSource.getAccessPath().isStaticFieldRef())
passOn = false;
boolean isPrimitiveOrString = false;
// Primitive types and strings cannot
// have aliases and thus never need to
// be propagated back
if (source.getAccessPath().getBaseType() instanceof PrimType)
isPrimitiveOrString = true;
if (TypeUtils.isStringType(source.getAccessPath().getBaseType())
&& !source.getAccessPath().getCanHaveImmutableAliases())
isPrimitiveOrString = true;
// we only can remove the taint if we step into the
// call/return edges
// otherwise we will loose taint - see
// ArrayTests/arrayCopyTest
if (passOn && hasValidCallees && !isPrimitiveOrString
&& (manager.getConfig().getInspectSources() || !isSource)
&& (manager.getConfig().getInspectSinks() || !isSink)
&& newSource.getAccessPath().isInstanceFieldRef()) {
// If one of the callers does not read the value, we
// must pass it on in any case
Collection callees = interproceduralCFG().getCalleesOfCallAt(call);
boolean allCalleesRead = true;
outer: for (SootMethod callee : callees) {
if (callee.isConcrete() && callee.hasActiveBody()) {
Set calleeAPs = mapAccessPathToCallee(callee, iCallStmt, invExpr, null,
null, source.getAccessPath());
if (calleeAPs != null) {
for (AccessPath ap : calleeAPs) {
if (ap != null) {
if (!interproceduralCFG().methodReadsValue(callee,
ap.getPlainValue())) {
allCalleesRead = false;
break outer;
}
}
}
}
}
// Additional check: If all callees are library
// classes, we pass it on as well
if (isExcluded(callee)) {
allCalleesRead = false;
break;
}
}
if (allCalleesRead) {
if (invExpr instanceof InstanceInvokeExpr
&& aliasing.mayAlias(((InstanceInvokeExpr) invExpr).getBase(),
newSource.getAccessPath().getPlainValue())) {
passOn = false;
}
if (passOn) {
for (int i = 0; i < callArgs.length; i++) {
if (aliasing.mayAlias(callArgs[i], newSource.getAccessPath().getPlainValue())) {
passOn = false;
break;
}
}
}
}
}
// If the callee does not read the given value, we also
// need to pass it on since we do not propagate it into
// the callee.
if (source.getAccessPath().isStaticFieldRef()) {
if (!interproceduralCFG().isStaticFieldUsed(callee, source.getAccessPath().getFirstField()))
passOn = true;
}
// Implicit taints are always passed over conditionally
// called methods
passOn |= source.getTopPostdominator() != null || source.getAccessPath().isEmpty();
if (passOn) {
if (newSource != getZeroValue())
res.add(newSource);
}
if (callee.isNative() && ncHandler != null)
for (Value callVal : callArgs)
if (callVal == newSource.getAccessPath().getPlainValue()) {
// java uses call by value, but fields of
// complex objects can be changed (and
// tainted), so use this conservative
// approach:
Set nativeAbs = ncHandler.getTaintedValues(iCallStmt, newSource,
callArgs);
if (nativeAbs != null) {
res.addAll(nativeAbs);
// Compute the aliases
for (Abstraction abs : nativeAbs)
if (abs.getAccessPath().isStaticFieldRef() || aliasing.canHaveAliases(
iCallStmt, abs.getAccessPath().getCompleteValue(), abs))
aliasing.computeAliases(d1, iCallStmt,
abs.getAccessPath().getPlainValue(), res,
interproceduralCFG().getMethodOf(call), abs);
}
// We only call the native code handler once
// per statement
break;
}
for (Abstraction abs : res)
if (abs != newSource)
abs.setCorrespondingCallSite(iCallStmt);
return res;
}
};
}
/**
* Maps the given access path into the scope of the callee
*
* @param callee The method that is being called
* @param stmt The caller statement or null
* @param ie The invocation expression for the call
* @param paramLocals The list of parameter locals in the callee
* @param thisLocal The "this" local in the callee
* @param ap The caller-side access path to map
* @return The set of callee-side access paths corresponding to the given
* caller-side access path
*/
private Set mapAccessPathToCallee(final SootMethod callee, Stmt stmtCaller, final InvokeExpr ie,
Value[] paramLocals, Local thisLocal, AccessPath ap) {
// We do not transfer empty access paths
if (ap.isEmpty())
return null;
// Android executor methods are handled specially.
// getSubSignature() is slow, so we try to avoid it whenever we can
final ICallerCalleeArgumentMapper mapper = CallerCalleeManager.getMapper(manager, stmtCaller, callee);
Set res = null;
// If we can't reason about aliases, there's little we can do here
final Aliasing aliasing = manager.getAliasing();
if (aliasing == null)
return null;
// If we are performing lazy aliasing, we need to retain all
// taints
if (aliasing.getAliasingStrategy().isLazyAnalysis() && Aliasing.canHaveAliases(ap)) {
res = new HashSet<>();
res.add(ap);
}
// Is this a virtual method call?
Value baseLocal = null;
if (!ap.isStaticFieldRef() && !callee.isStatic()) {
baseLocal = mapper.getCallerValueOfCalleeParameter(ie, ICallerCalleeArgumentMapper.BASE_OBJECT);
}
// If we have a base local to map, we need to find the
// corresponding "this" local
if (baseLocal != null) {
if (aliasing.mayAlias(baseLocal, ap.getPlainValue()))
if (manager.getTypeUtils().hasCompatibleTypesForCall(ap, callee.getDeclaringClass())) {
if (res == null)
res = new HashSet();
// Get the "this" local if we don't have it yet
if (thisLocal == null)
thisLocal = callee.getActiveBody().getThisLocal();
res.add(manager.getAccessPathFactory().copyWithNewValue(ap, thisLocal));
}
}
final int calleeParamCount = callee.getParameterCount();
if (calleeParamCount > 0) {
for (int i = 0; i < ie.getArgCount(); i++) {
if (aliasing.mayAlias(ie.getArg(i), ap.getPlainValue())) {
if (res == null)
res = new HashSet();
int mapped = mapper.getCalleeIndexOfCallerParameter(i);
if (mapped == ICallerCalleeArgumentMapper.UNKNOWN)
continue;
// Get the parameter locals if we don't have them yet
if (paramLocals == null)
paramLocals = callee.getActiveBody().getParameterLocals()
.toArray(new Local[calleeParamCount]);
if (mapped == ICallerCalleeArgumentMapper.ALL_PARAMS) {
// Reflection
// Taint all parameters in the callee if the argument array of a reflective
// method call is tainted
for (int j = 0; j < paramLocals.length; j++) {
AccessPath newAP = manager.getAccessPathFactory().copyWithNewValue(ap,
paramLocals[j], null, false);
if (newAP != null)
res.add(newAP);
}
continue;
} else if (mapped == ICallerCalleeArgumentMapper.BASE_OBJECT)
continue;
else {
// Taint the corresponding parameter local in the callee
AccessPath newAP = manager.getAccessPathFactory().copyWithNewValue(ap,
paramLocals[mapped]);
if (newAP != null)
res.add(newAP);
}
}
}
// Sometimes callers have more arguments than the callee parameters, e.g.
// because one argument is resolved in native code. A concrete example is
// sendMessageDelayed(android.os.Message, int)
// -> handleMessage(android.os.Message message)
// TODO: handle argument/parameter mismatch for some special cases
}
return res;
}
};
}
}