Skip to content

Commit

Permalink
Report IncompleteOperationErrors only once for each dataflow path wit…
Browse files Browse the repository at this point in the history
…h a missing call
  • Loading branch information
smeyer198 committed Mar 10, 2024
1 parent afa3a3c commit 9d60b5b
Show file tree
Hide file tree
Showing 6 changed files with 230 additions and 107 deletions.
Original file line number Diff line number Diff line change
@@ -1,31 +1,18 @@
package crypto.analysis;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

import boomerang.callgraph.ObservableICFG;
import boomerang.debugger.Debugger;
import boomerang.jimple.AllocVal;
import boomerang.jimple.Statement;
import boomerang.jimple.Val;
import boomerang.results.ForwardBoomerangResults;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
import com.google.common.collect.Table;
import com.google.common.collect.Table.Cell;

import boomerang.callgraph.ObservableICFG;
import boomerang.debugger.Debugger;
import boomerang.jimple.AllocVal;
import boomerang.jimple.Statement;
import boomerang.jimple.Val;
import boomerang.results.ForwardBoomerangResults;
import crypto.analysis.errors.AbstractError;
import crypto.analysis.errors.IncompleteOperationError;
import crypto.analysis.errors.RequiredPredicateError;
Expand Down Expand Up @@ -61,14 +48,25 @@
import soot.jimple.Constant;
import soot.jimple.IntConstant;
import soot.jimple.InvokeExpr;
import soot.jimple.Stmt;
import soot.jimple.StringConstant;
import soot.jimple.ThrowStmt;
import sync.pds.solver.nodes.Node;
import typestate.TransitionFunction;
import typestate.finiteautomata.ITransition;
import typestate.finiteautomata.State;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.stream.Collectors;

public class AnalysisSeedWithSpecification extends IAnalysisSeed {

private final ClassSpecification spec;
Expand Down Expand Up @@ -182,9 +180,10 @@ private void computeTypestateErrorUnits() {

private void computeTypestateErrorsForEndOfObjectLifeTime() {
Table<Statement, Val, TransitionFunction> endPathOfPropagation = results.getObjectDestructingStatements();
Map<Statement, Set<SootMethod>> incompleteOperations = new HashMap<>();

for (Cell<Statement, Val, TransitionFunction> c : endPathOfPropagation.cellSet()) {
Set<SootMethod> expectedMethodsToBeCalled = Sets.newHashSet();
Set<SootMethod> expectedMethodsToBeCalled = new HashSet<>();

for (ITransition n : c.getValue().values()) {
if (n.to() == null) {
Expand All @@ -209,18 +208,89 @@ private void computeTypestateErrorsForEndOfObjectLifeTime() {
}

if (!expectedMethodsToBeCalled.isEmpty()) {
Statement s = c.getRowKey();
Val val = c.getColumnKey();
incompleteOperations.put(c.getRowKey(), expectedMethodsToBeCalled);
}
}

// No incomplete operations were found
if (incompleteOperations.entrySet().isEmpty()) {
return;
}

/* If there is only one incomplete operation, then there is only one dataflow path. Hence,
* the error can be reported directly.
*/
if (incompleteOperations.entrySet().size() == 1) {
Entry<Statement, Set<SootMethod>> entry = incompleteOperations.entrySet().iterator().next();
Statement s = entry.getKey();
Set<SootMethod> methodsToBeCalled = entry.getValue();

if (!s.getUnit().isPresent()) {
return;
}

if (s.getUnit().get() instanceof ThrowStmt) {
return;
}

IncompleteOperationError incompleteOperationError = new IncompleteOperationError(this, s, spec.getRule(), methodsToBeCalled);
this.addError(incompleteOperationError);
cryptoScanner.getAnalysisListener().reportError(this, incompleteOperationError);
}

/* Multiple incomplete operations were found. Depending on the dataflow paths, the
* errors are reported:
* 1) A dataflow path ends in an accepting state: No error is reported
* 2) A dataflow path does not end in an accepting state: Report the error on the last used statement on this path
*/
if (incompleteOperations.size() > 1) {
for (Entry<Statement, Set<SootMethod>> entry : incompleteOperations.entrySet()) {
Statement statement = entry.getKey();
Set<SootMethod> expectedMethodsToBeCalled = entry.getValue();

// Extract the called SootMethod from the statement
if (!statement.getUnit().isPresent()) {
continue;
}

if (statement.getUnit().get() instanceof ThrowStmt) {
continue;
}

if (!statement.getUnit().get().containsInvokeExpr()) {
continue;
}

if (!(s.getUnit().get() instanceof ThrowStmt)) {
IncompleteOperationError incompleteOperationError = new IncompleteOperationError(s, val, getSpec().getRule(), this, expectedMethodsToBeCalled);
this.addError(incompleteOperationError);
cryptoScanner.getAnalysisListener().reportError(this, incompleteOperationError);
// Only if the path does not end in an accepting state, the error should be reported
InvokeExpr invokeExpr = statement.getUnit().get().getInvokeExpr();
if (isMethodToAcceptingState(invokeExpr.getMethod())) {
continue;
}

IncompleteOperationError incompleteOperationError = new IncompleteOperationError(this, statement, spec.getRule(), expectedMethodsToBeCalled, true);
this.addError(incompleteOperationError);
cryptoScanner.getAnalysisListener().reportError(this, incompleteOperationError);
}
}
}

private boolean isMethodToAcceptingState(SootMethod method) {
Collection<TransitionEdge> transitions = spec.getRule().getUsagePattern().getAllTransitions();
Collection<CrySLMethod> methods = CrySLMethodToSootMethod.v().convert(method);

for (TransitionEdge edge : transitions) {
if (edge.getLabel().stream().noneMatch(e -> methods.contains(e))) {
continue;
}

if (edge.to().getAccepting()) {
return true;
}
}

return false;
}

private void typeStateChangeAtStatement(Statement curr, State stateNode) {
if (typeStateChange.put(curr, stateNode)) {
if (stateNode instanceof ReportingErrorStateNode) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,52 +1,69 @@
package crypto.analysis.errors;

import java.util.Collection;
import java.util.HashSet;
import java.util.Set;

import boomerang.jimple.Statement;
import com.google.common.base.Joiner;
import com.google.common.base.Optional;
import com.google.common.collect.Sets;

import boomerang.jimple.Statement;
import boomerang.jimple.Val;
import crypto.analysis.IAnalysisSeed;
import crypto.rules.CrySLRule;
import soot.SootMethod;
import soot.jimple.InvokeExpr;
import soot.jimple.Stmt;

import java.util.Collection;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;

/** This class defines-IncompleteOperationError:
*
*Found when the usage of an object may be incomplete

/** <p>This class defines-IncompleteOperationError:</p>
*
*For example a Cipher object may be initialized but never been used for encryption or decryption, this may render the code dead.
*This error heavily depends on the computed call graph (CHA by default)
* <p>Found when the usage of an object may be incomplete. If there are
* multiple paths, and at least one path introduces an incomplete operation,
* the analysis indicates that there is a potential path with missing calls.</p>
*
* */

* <p>For example a Cipher object may be initialized but never been used for encryption or decryption, this may render the code dead.
* This error heavily depends on the computed call graph (CHA by default).</p>
*/
public class IncompleteOperationError extends ErrorWithObjectAllocation{

private Val errorVariable;
private Collection<SootMethod> expectedMethodCalls;
private Set<String> expectedMethodCallsSet = Sets.newHashSet();
private final Collection<SootMethod> expectedMethodCalls;
private final Set<String> expectedMethodCallsSet;
private final boolean multiplePaths;

public IncompleteOperationError(Statement errorLocation,
Val errorVariable, CrySLRule rule, IAnalysisSeed objectLocation, Collection<SootMethod> expectedMethodsToBeCalled) {
/**
* Create an IncompleteOperationError, if there is only one dataflow path, where the
* incomplete operation occurs.
*
* @param objectLocation the seed for the incomplete operation
* @param errorLocation the statement of the last usage of the seed
* @param rule the CrySL rule for the seed
* @param expectedMethodsToBeCalled the methods that are expected to be called
*/
public IncompleteOperationError(IAnalysisSeed objectLocation, Statement errorLocation, CrySLRule rule, Collection<SootMethod> expectedMethodsToBeCalled) {
this(objectLocation, errorLocation, rule, expectedMethodsToBeCalled, false);
}

/**
* Create an IncompleteOperationError, if there is at least one dataflow path, where an
* incomplete operation occurs.
*
* @param objectLocation the seed for the incomplete operation
* @param errorLocation the statement of the last usage of the seed
* @param rule the CrySL rule for the seed
* @param expectedMethodsToBeCalled the methods that are expected to be called
* @param multiplePaths set to true, if there are multiple paths (default: false)
*/
public IncompleteOperationError(IAnalysisSeed objectLocation, Statement errorLocation, CrySLRule rule, Collection<SootMethod> expectedMethodsToBeCalled, boolean multiplePaths) {
super(errorLocation, rule, objectLocation);
this.errorVariable = errorVariable;
this.expectedMethodCalls = expectedMethodsToBeCalled;

this.expectedMethodCalls = expectedMethodsToBeCalled;
this.multiplePaths = multiplePaths;

this.expectedMethodCallsSet = new HashSet<>();
for (SootMethod method : expectedMethodCalls) {
this.expectedMethodCallsSet.add(method.getSignature());
}
}
}

public Val getErrorVariable() {
return errorVariable;
}

public Collection<SootMethod> getExpectedMethodCalls() {
return expectedMethodCalls;
}
Expand All @@ -57,21 +74,52 @@ public void accept(ErrorVisitor visitor){

@Override
public String toErrorMarkerString() {
Collection<SootMethod> expectedCalls = getExpectedMethodCalls();
final StringBuilder msg = new StringBuilder();
if (multiplePaths) {
return getErrorMarkerStringForMultipleDataflowPaths();
} else {
return getErrorMarkerStringForSingleDataflowPath();
}
}

private String getErrorMarkerStringForSingleDataflowPath() {
StringBuilder msg = new StringBuilder();
msg.append("Operation");
msg.append(getObjectType());
msg.append(" object not completed. Expected call to ");
final Set<String> altMethods = new HashSet<>();
for (final SootMethod expectedCall : expectedCalls) {
msg.append(" not completed. Expected call to ");

Set<String> altMethods = getFormattedExpectedCalls();
msg.append(Joiner.on(", ").join(altMethods));
return msg.toString();
}

private String getErrorMarkerStringForMultipleDataflowPaths() {
if (!getErrorLocation().isCallsite() || !getErrorLocation().getUnit().isPresent()) {
return "Unable to describe error";
}
StringBuilder msg = new StringBuilder();
msg.append("Call to ");

InvokeExpr invokeExpr = getErrorLocation().getUnit().get().getInvokeExpr();
msg.append(invokeExpr.getMethod().getName());
msg.append(getObjectType());
msg.append(" is on a dataflow path with an incomplete operation. Potential missing call to ");

Set<String> altMethods = getFormattedExpectedCalls();
msg.append(Joiner.on(", ").join(altMethods));
return msg.toString();
}

private Set<String> getFormattedExpectedCalls() {
Set<String> altMethods = new HashSet<>();

for (SootMethod expectedCall : getExpectedMethodCalls()) {
if (stmtInvokesExpectedCallName(expectedCall.getName())){
altMethods.add(expectedCall.getSignature().replace("<", "").replace(">", ""));
} else {
altMethods.add(expectedCall.getName().replace("<", "").replace(">", ""));
}
}
msg.append(Joiner.on(", ").join(altMethods));
return msg.toString();
return altMethods;
}

/**
Expand All @@ -80,19 +128,24 @@ public String toErrorMarkerString() {
*/
private boolean stmtInvokesExpectedCallName(String expectedCallName){
Statement errorLocation = getErrorLocation();
if (errorLocation.isCallsite()){
Optional<Stmt> stmtOptional = errorLocation.getUnit();
if (stmtOptional.isPresent()){
Stmt stmt = stmtOptional.get();
if (stmt.containsInvokeExpr()){
InvokeExpr call = stmt.getInvokeExpr();
SootMethod calledMethod = call.getMethod();
if (calledMethod.getName().equals(expectedCallName)){
return true;
}
}
}

if (!errorLocation.isCallsite()) {
return false;
}

Optional<Stmt> stmtOptional = errorLocation.getUnit().toJavaUtil();

if (!stmtOptional.isPresent()) {
return false;
}

Stmt stmt = stmtOptional.get();
if (stmt.containsInvokeExpr()) {
InvokeExpr call = stmt.getInvokeExpr();
SootMethod calledMethod = call.getMethod();
return calledMethod.getName().equals(expectedCallName);
}

return false;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public void testBCMacExamples() {
setErrorsCount("<gwt_crypto.SkeinMacTest: void performTestOne()>", RequiredPredicateError.class, 1);

setErrorsCount("<bunkr.PBKDF2Descriptor: int calculateRounds(int)>", TypestateError.class, 1);
setErrorsCount("<bunkr.PBKDF2Descriptor: int calculateRounds(int)>", IncompleteOperationError.class, 2);
setErrorsCount("<bunkr.PBKDF2Descriptor: int calculateRounds(int)>", IncompleteOperationError.class, 1);

setErrorsCount("<pattern.MacTest: void testMac1()>", RequiredPredicateError.class, 1);
setErrorsCount("<pattern.MacTest: void testMac2()>", RequiredPredicateError.class, 3);
Expand Down
Loading

0 comments on commit 9d60b5b

Please sign in to comment.