507 lines
20 KiB
Java
507 lines
20 KiB
Java
/*
|
|
* (C) Copyright 2010-2015 SAP SE.
|
|
* 2016 The University of Sheffield.
|
|
*
|
|
* All rights reserved. This program and the accompanying materials
|
|
* are made available under the terms of the Eclipse Public License v1.0
|
|
* which accompanies this distribution, and is available at
|
|
* http://www.eclipse.org/legal/epl-v10.html
|
|
*
|
|
*/
|
|
|
|
package com.logicalhacking.dasca.dataflow.util;
|
|
|
|
import java.io.BufferedInputStream;
|
|
import java.io.File;
|
|
import java.io.FileInputStream;
|
|
import java.io.FileNotFoundException;
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.net.URL;
|
|
import java.util.ArrayList;
|
|
import java.util.Collection;
|
|
import java.util.Collections;
|
|
import java.util.HashMap;
|
|
import java.util.HashSet;
|
|
import java.util.Properties;
|
|
|
|
import org.apache.log4j.Logger;
|
|
import org.apache.log4j.PropertyConfigurator;
|
|
|
|
|
|
import com.ibm.wala.cast.java.loader.JavaSourceLoaderImpl.ConcreteJavaMethod;
|
|
import com.ibm.wala.cast.java.ssa.AstJavaInvokeInstruction;
|
|
import com.ibm.wala.classLoader.IClass;
|
|
import com.ibm.wala.classLoader.IMethod;
|
|
import com.ibm.wala.classLoader.Language;
|
|
import com.ibm.wala.ipa.callgraph.AnalysisCache;
|
|
import com.ibm.wala.ipa.callgraph.AnalysisCacheImpl;
|
|
import com.ibm.wala.ipa.callgraph.AnalysisOptions;
|
|
import com.ibm.wala.ipa.callgraph.AnalysisScope;
|
|
import com.ibm.wala.ipa.callgraph.CallGraph;
|
|
import com.ibm.wala.ipa.callgraph.Entrypoint;
|
|
import com.ibm.wala.ipa.callgraph.impl.DefaultEntrypoint;
|
|
import com.ibm.wala.ipa.callgraph.propagation.SSAPropagationCallGraphBuilder;
|
|
import com.ibm.wala.ipa.cfg.BasicBlockInContext;
|
|
import com.ibm.wala.ipa.cha.ClassHierarchyFactory;
|
|
import com.ibm.wala.ipa.cha.ClassHierarchy;
|
|
import com.ibm.wala.ssa.DefUse;
|
|
import com.ibm.wala.ssa.SSABinaryOpInstruction;
|
|
import com.ibm.wala.ssa.SSAConditionalBranchInstruction;
|
|
import com.ibm.wala.ssa.SSAInstruction;
|
|
import com.ibm.wala.ssa.SSAPhiInstruction;
|
|
import com.ibm.wala.ssa.SymbolTable;
|
|
import com.ibm.wala.ssa.analysis.IExplodedBasicBlock;
|
|
import com.ibm.wala.types.ClassLoaderReference;
|
|
import com.ibm.wala.types.TypeName;
|
|
import com.ibm.wala.util.collections.HashSetFactory;
|
|
import com.ibm.wala.util.config.AnalysisScopeReader;
|
|
|
|
/**
|
|
* static class for graph independent helper methods and constants
|
|
*
|
|
*/
|
|
public class AnalysisUtil {
|
|
|
|
private static final Logger log = AnalysisUtil.getLogger(AnalysisUtil.class);
|
|
|
|
/**
|
|
* Relative path to the main configuration file
|
|
*/
|
|
public static final String MAIN_CONFIG = "config/main.config";
|
|
|
|
/**
|
|
* Relative path to the logging configuration file
|
|
*/
|
|
public static final String LOGGING_FILE = "logging_properties_file";
|
|
|
|
/**
|
|
* Boolean value of configuration file, if subgraphs should be included into the DOT file
|
|
*/
|
|
public static final String CONFIG_BOOLEAN_PRINT_SUBGRAPHS = "bool_print_subgraphs";
|
|
|
|
/**
|
|
* DOT files will be generated into the specified sub directory of the working directory
|
|
*/
|
|
public static final String CONFIG_DOT_PATH = "dot_path";
|
|
|
|
/**
|
|
* DOT files will be generated into the specified sub directory of the working directory
|
|
*/
|
|
public static final String CONFIG_DOT_REMOVE_EMPTY_NODES = "dot_remove_empty_nodes";
|
|
|
|
/**
|
|
* Specifies which project contains the entry class
|
|
*/
|
|
public static final String CONFIG_ANALYSIS_PROJECT = "analysis_project";
|
|
|
|
/**
|
|
* Specifies which class contains the entry method
|
|
*/
|
|
public static final String CONFIG_ENTRY_CLASS = "analysis_entry_class";
|
|
|
|
/**
|
|
* Specifies the entry methods of the analysis
|
|
*/
|
|
public static final String CONFIG_ENTRY_METHOD = "analysis_entry_method";
|
|
|
|
/**
|
|
* Specifies sanitizing methods of the analysis
|
|
*/
|
|
public static final String CONFIG_SANITIZER = "analysis_sanitizer";
|
|
|
|
/**
|
|
* Specifies bad source methods of the analysis
|
|
*/
|
|
public static final String CONFIG_BAD_SRC = "analysis_bad_src";
|
|
|
|
/**
|
|
* Specifies the depth (precision) of the analysis
|
|
*/
|
|
public static final String CONFIG_ANALYSIS_DEPTH = "analysis_depth";
|
|
public static final int ANALYSIS_DEPTH_DETECTION = 0;
|
|
public static final int ANALYSIS_DEPTH_EXCLUSIVE = 1;
|
|
public static final int ANALYSIS_DEPTH_SANITIZING = 2;
|
|
|
|
/**
|
|
* Specifies where to find the exclusion file
|
|
*/
|
|
public static final String CONFIG_EXCLUSION_FILE = "analysis_exclusion_file";
|
|
|
|
/**
|
|
* Gets callgraph for given parameters (binary analysis only)
|
|
* @param exclusionFilePath
|
|
* @param classPath
|
|
* @param entryClass
|
|
* @param entryMethod
|
|
* @return
|
|
*/
|
|
public static CallGraph getCallGraph(String exclusionFilePath, String classPath, String entryClass, String entryMethod) {
|
|
AnalysisScope scope = null;
|
|
ClassHierarchy cha = null;
|
|
HashSet<Entrypoint> entryPoints = null;
|
|
try {
|
|
File exclusionFile = new File(exclusionFilePath);
|
|
scope = AnalysisScopeReader.makeJavaBinaryAnalysisScope(classPath, exclusionFile); // works with class and jar files
|
|
cha = ClassHierarchyFactory.make(scope);
|
|
|
|
ClassLoaderReference clr = scope.getApplicationLoader();
|
|
entryPoints = HashSetFactory.make();
|
|
for(IClass class1 : cha) {
|
|
if(class1.getClassLoader().getReference().equals(clr)) {
|
|
Collection<? extends IMethod> allMethods = class1.getDeclaredMethods();
|
|
for(IMethod m : allMethods) {
|
|
if(m.isPrivate()) {
|
|
continue;
|
|
}
|
|
TypeName tn = m.getDeclaringClass().getName();//MainApplication
|
|
if(tn.toString().contains("/" + entryClass) && m.getName().toString().contains(entryMethod)) { // TODO: too weak
|
|
entryPoints.add(new DefaultEntrypoint(m, cha));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Iterable<Entrypoint> result1 = com.ibm.wala.ipa.callgraph.impl.Util.makeMainEntrypoints(scope, cha); // uses the static main methods as entry methods
|
|
if(entryPoints.size() == 0) {
|
|
log.error("Could not find specified entry point for analysis.\n" +
|
|
" path: " + classPath + "\n" +
|
|
" class: " + entryClass + "\n" +
|
|
" method: " + entryMethod);
|
|
System.exit(1);
|
|
}
|
|
AnalysisOptions options = new AnalysisOptions(scope, entryPoints);
|
|
|
|
// CallGraphBuilder builder = com.ibm.wala.ipa.callgraph.impl.Util.makeRTABuilder(options, new AnalysisCacheImpl(), cha, scope); // Rapid Type Analysis
|
|
SSAPropagationCallGraphBuilder builder = com.ibm.wala.ipa.callgraph.impl.Util.makeZeroCFABuilder(Language.JAVA, options, new AnalysisCacheImpl(), cha, scope); // 0-CFA = context-insensitive, class-based heap
|
|
// CallGraphBuilder builder = com.ibm.wala.ipa.callgraph.impl.Util.makeZeroOneCFABuilder(options, new AnalysisCacheImpl(), cha, scope); // 0-1-CFA = context-insensitive, allocation-site-based heap
|
|
// CallGraphBuilder builder = com.ibm.wala.ipa.callgraph.impl.Util.makeZeroOneContainerCFABuilder(options, new AnalysisCacheImpl(), cha, scope); // 0-1-Container-CFA = object-sensitive container
|
|
|
|
return builder.makeCallGraph(options);
|
|
} catch (Exception e) {
|
|
log.error("Error while building the call graph");
|
|
e.printStackTrace();
|
|
System.exit(1);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets the corresponding integer value of the given property name, or 0 if no integer value was found
|
|
* @param name
|
|
* @return
|
|
*/
|
|
public static int getPropertyInteger(String name) {
|
|
String value = getPropertyString(name);
|
|
try {
|
|
return Integer.parseInt(value);
|
|
} catch (NumberFormatException e) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets the corresponding boolean value of the given property name, or false if no value was found
|
|
* @param name
|
|
* @return
|
|
*/
|
|
public static boolean getPropertyBoolean(String name) {
|
|
String value = getPropertyString(name);
|
|
return value.equalsIgnoreCase("yes") | value.equalsIgnoreCase("true");
|
|
}
|
|
|
|
/**
|
|
* Gets the corresponding value of the given property name, or an empty String if no value was found
|
|
* @param name
|
|
* @return
|
|
*/
|
|
public static String getPropertyString(String name) {
|
|
Properties properties = new Properties();
|
|
properties.setProperty("MAIN_CONFIG", MAIN_CONFIG);
|
|
BufferedInputStream stream;
|
|
try {
|
|
InputStream in;
|
|
File f = new File(MAIN_CONFIG);
|
|
in = new FileInputStream(f);
|
|
System.err.println("Reading configuration file: "+f);
|
|
stream = new BufferedInputStream(in);
|
|
properties.load(stream);
|
|
stream.close();
|
|
} catch (FileNotFoundException e) {
|
|
log.warn("Warning: no config file found:", e);
|
|
properties.setProperty("MAIN_CONFIG", MAIN_CONFIG + " (not found)");
|
|
} catch (IOException e) {
|
|
log.error("Failure reading no config file:", e);
|
|
properties.setProperty("MAIN_CONFIG", MAIN_CONFIG + " (reading error)");
|
|
}
|
|
String value = properties.getProperty(name);
|
|
return value == null ? "" : value;
|
|
}
|
|
|
|
/**
|
|
* Removes all chars from the string which confuse dotty <br />
|
|
* Currently '<' and '>' <br />
|
|
* Expand this if you run into trouble with other chars
|
|
* @param input
|
|
* @return
|
|
*/
|
|
public static String sanitize(String input) {
|
|
String output = "";
|
|
|
|
for (char c : input.toCharArray()) {
|
|
if (!(c == '<' || c == '>')) {
|
|
output += c;
|
|
} else {
|
|
output += ' ';
|
|
}
|
|
}
|
|
return output;
|
|
}
|
|
|
|
/**
|
|
* Get root logger
|
|
* @param class
|
|
* @return
|
|
*/
|
|
public static org.apache.log4j.Logger getLogger(Class<?> class1) {
|
|
Logger log = Logger.getLogger(class1);
|
|
PropertyConfigurator.configure(getPropertyString(LOGGING_FILE));
|
|
return log;
|
|
}
|
|
|
|
/**
|
|
* Get source code line number for each instruction
|
|
* @param sgNodes
|
|
* @param print
|
|
* @return
|
|
*/
|
|
public static HashMap<SSAInstructionKey, Integer> getLineNumbers(HashMap<Integer,BasicBlockInContext<IExplodedBasicBlock>> sgNodes) {
|
|
log.debug("** get source code line number for each instruction");
|
|
HashMap<SSAInstructionKey, Integer> map = new HashMap<SSAInstructionKey, Integer>();
|
|
for(BasicBlockInContext<IExplodedBasicBlock> bbic : sgNodes.values()) {
|
|
SSAInstruction inst = bbic.getLastInstruction();
|
|
if(inst == null) {
|
|
continue;
|
|
}
|
|
// ConcreteJavaMethod method = (ConcreteJavaMethod) bbic.getMethod();
|
|
IMethod method = bbic.getMethod();
|
|
int lineNumber = method.getLineNumber(bbic.getLastInstructionIndex());
|
|
map.put(new SSAInstructionKey(inst), lineNumber);
|
|
log.debug(lineNumber + ". " + inst);
|
|
}
|
|
return map;
|
|
}
|
|
|
|
/**
|
|
* Get corresponding instruction for the definition of each SSA value
|
|
* @param sgNodes
|
|
* @return
|
|
*/
|
|
public static HashMap<Integer, SSAInstruction> getDefs(HashMap<Integer,BasicBlockInContext<IExplodedBasicBlock>> sgNodes) {
|
|
log.debug("** get definition instruction for each SSA value");
|
|
HashMap<Integer, SSAInstruction> map = new HashMap<Integer, SSAInstruction>();
|
|
for(BasicBlockInContext<IExplodedBasicBlock> bbic : sgNodes.values()) {
|
|
SymbolTable symbolTable = bbic.getNode().getIR().getSymbolTable();
|
|
DefUse du = bbic.getNode().getDU();
|
|
|
|
for (int i = 0; i <= symbolTable.getMaxValueNumber(); i++) {
|
|
log.debug(i + " [" + symbolTable.getValueString(i) + "] " + du.getDef(i));
|
|
map.put(i, du.getDef(i));
|
|
}
|
|
break; // there are the same definitions in each basic block, iff there is only one method FIXME: read different scopes, if multiple methods are required
|
|
}
|
|
return map;
|
|
}
|
|
|
|
/**
|
|
* Get the corresponding instruction for each SQL execution (java.sql)
|
|
* @return
|
|
*/
|
|
public static ArrayList<SSAInstruction> getSQLExecutes(HashMap<Integer, BasicBlockInContext<IExplodedBasicBlock>> sgNodes) {
|
|
log.debug("** get SQL execution instructions");
|
|
ArrayList<SSAInstruction> list = new ArrayList<SSAInstruction>();
|
|
for(BasicBlockInContext<IExplodedBasicBlock> bbic : sgNodes.values()) {
|
|
SSAInstruction inst = bbic.getLastInstruction();
|
|
if(inst != null && inst instanceof AstJavaInvokeInstruction && inst.toString().contains("java/sql") && inst.toString().contains("execute")) {
|
|
log.debug("SQL execution instruction: " + inst.toString());
|
|
list.add(inst);
|
|
}
|
|
}
|
|
return list;
|
|
}
|
|
|
|
/**
|
|
* Get the corresponding instruction for each conditional branch
|
|
* @return
|
|
*/
|
|
public static ArrayList<SSAInstruction> getConditions(HashMap<Integer, BasicBlockInContext<IExplodedBasicBlock>> sgNodes) {
|
|
log.debug("** get conditional branch instructions");
|
|
ArrayList<SSAInstruction> list = new ArrayList<SSAInstruction>();
|
|
for(BasicBlockInContext<IExplodedBasicBlock> bbic : sgNodes.values()) {
|
|
SSAInstruction inst = bbic.getLastInstruction();
|
|
if(inst != null && inst instanceof SSAConditionalBranchInstruction) {
|
|
log.debug("conditional branch instruction: " + inst);
|
|
list.add(inst);
|
|
}
|
|
}
|
|
return list;
|
|
}
|
|
|
|
|
|
/**
|
|
* Prints adjacency list to log using debug level
|
|
* @param adjList
|
|
*/
|
|
public static void printAdjList(HashMap<Integer, ArrayList<Integer>> adjList, Logger log) {
|
|
ArrayList<Integer> keySet1 = new ArrayList<Integer>(adjList.keySet());
|
|
Collections.sort(keySet1);
|
|
for (Integer src: keySet1) {
|
|
log.debug(" " + src + ":");
|
|
StringBuffer sb = new StringBuffer();
|
|
for(Integer dest : adjList.get(src)) {
|
|
sb.append(", " + dest);
|
|
}
|
|
sb.append(" ");
|
|
log.debug(" " + sb.substring(1));
|
|
}
|
|
}
|
|
|
|
public static ArrayList<SSAInstruction> analyzeStatementExecute(
|
|
SSAInstruction ssaInstruction,
|
|
HashMap<Integer, SSAInstruction> definitions,
|
|
boolean prepared,
|
|
HashSet<String> badMethods) {
|
|
|
|
if(prepared) {
|
|
ssaInstruction = definitions.get(ssaInstruction.getUse(0));
|
|
}
|
|
|
|
ArrayList<SSAInstruction> sources = new ArrayList<SSAInstruction>();
|
|
int use = ssaInstruction.getUse(1); // use 0 is the createStatement, use 1 is the SQL String
|
|
SSAInstruction inst = definitions.get(use);
|
|
if(inst != null) {
|
|
String instString = inst.toString();
|
|
if(isBadSource(instString, badMethods)) {
|
|
if(!sources.contains(inst)) {
|
|
log.debug("SINK [bad]: " + ssaInstruction);
|
|
sources.add(inst);
|
|
}
|
|
}
|
|
if(inst instanceof SSABinaryOpInstruction) {
|
|
if(isBadBinaryOpSource(use, definitions, sources, badMethods)) {
|
|
log.debug("SINK [bad]: " + ssaInstruction);
|
|
return sources;
|
|
} else {
|
|
log.debug("SINK [good]: " + ssaInstruction);
|
|
}
|
|
} else if(inst instanceof SSAPhiInstruction) {
|
|
if(isBadPhiInstruction(use, definitions, sources, badMethods)) {
|
|
log.debug("SINK [bad]: " + ssaInstruction);
|
|
return sources;
|
|
} else {
|
|
log.debug("SINK [good]: " + ssaInstruction);
|
|
}
|
|
} else { //FIXME: rewrite to according procedure
|
|
log.debug("unidentified instruction [" + instString + "], handled like phi function");
|
|
if(isBadPhiInstruction(use, definitions, sources, badMethods)) {
|
|
log.debug("SINK [bad]: " + ssaInstruction);
|
|
return sources;
|
|
} else {
|
|
log.debug("SINK [good]: " + ssaInstruction);
|
|
}
|
|
}
|
|
|
|
} else { // is constant String
|
|
log.debug("SINK [good]: " + ssaInstruction.toString());
|
|
}
|
|
return sources;
|
|
}
|
|
|
|
public static boolean isBadSource(String instString, HashSet<String> badMethods) {
|
|
if(instString != null) {
|
|
for (String src : badMethods) {
|
|
if(instString.contains(src)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private static boolean isBadBinaryOpSource(int use, HashMap<Integer,SSAInstruction> definitions, ArrayList<SSAInstruction> badSources, HashSet<String> badMethods) {
|
|
SSAInstruction inst = definitions.get(use);
|
|
int part1 = inst.getUse(0);
|
|
SSAInstruction inst1 = definitions.get(part1);
|
|
String inst1String = "";
|
|
if(inst1 != null) {
|
|
inst1String = inst1.toString();
|
|
}
|
|
|
|
int part2 = inst.getUse(1);
|
|
SSAInstruction inst2 = definitions.get(part2);
|
|
String inst2String = "";
|
|
if(inst2 != null) {
|
|
inst2String = inst2.toString();
|
|
}
|
|
|
|
boolean isBad = false;
|
|
if(inst1 == null) {
|
|
isBad = isBad | false;
|
|
} else if(inst1 instanceof SSABinaryOpInstruction) {
|
|
isBad = isBad | isBadBinaryOpSource(part1, definitions, badSources, badMethods);
|
|
} else if(inst1 instanceof SSAPhiInstruction) {
|
|
isBad = isBad | isBadPhiInstruction(part1, definitions, badSources, badMethods);
|
|
} else if(isBadSource(inst1String, badMethods)) {
|
|
if(!badSources.contains(inst1)) {
|
|
log.debug("SOURCE [bad]: " + inst1String);
|
|
badSources.add(inst1);
|
|
}
|
|
isBad = true;
|
|
}
|
|
if(inst2 == null) {
|
|
isBad = isBad | false;
|
|
} else if(inst2 instanceof SSABinaryOpInstruction) {
|
|
isBad = isBad | isBadBinaryOpSource(part2, definitions, badSources, badMethods);
|
|
} else if(inst2 instanceof SSAPhiInstruction) {
|
|
isBad = isBad | isBadPhiInstruction(part2, definitions, badSources, badMethods);
|
|
} else if(isBadSource(inst2String, badMethods)) {
|
|
if(!badSources.contains(inst2)) {
|
|
log.debug("SOURCE [bad]: " + inst2String);
|
|
badSources.add(inst2);
|
|
}
|
|
isBad = true;
|
|
}
|
|
return isBad;
|
|
}
|
|
|
|
|
|
private static boolean isBadPhiInstruction(int use,
|
|
HashMap<Integer, SSAInstruction> definitions,
|
|
ArrayList<SSAInstruction> badSources, HashSet<String> badMethods) {
|
|
SSAInstruction inst = definitions.get(use);
|
|
boolean isBad = false;
|
|
SSAPhiInstruction phiInst = (SSAPhiInstruction) inst;
|
|
for(int i=0; i<phiInst.getNumberOfUses(); i++) {
|
|
int part = phiInst.getUse(i);
|
|
SSAInstruction useInst = definitions.get(part);
|
|
String useInstString = "";
|
|
if(useInst != null) {
|
|
useInstString = useInst.toString();
|
|
}
|
|
if(useInst == null) {
|
|
isBad = isBad | false;
|
|
} else if(useInst instanceof SSABinaryOpInstruction) {
|
|
isBad = isBad | isBadBinaryOpSource(part, definitions, badSources, badMethods);
|
|
} else if(useInst instanceof SSAPhiInstruction) {
|
|
isBad = isBad | isBadPhiInstruction(part, definitions, badSources, badMethods);
|
|
} else if(isBadSource(useInstString, badMethods)) {
|
|
if(!badSources.contains(useInst)) {
|
|
log.debug("SOURCE [bad]: " + useInstString);
|
|
badSources.add(useInst);
|
|
}
|
|
isBad = true;
|
|
}
|
|
}
|
|
return isBad;
|
|
}
|
|
}
|