/** * Find Security Bugs * Copyright (c) Philippe Arteau, All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3.0 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. */ package com.h3xstream.findsecbugs.taintanalysis; import com.h3xstream.findsecbugs.FindSecBugsGlobalConfig; import edu.umd.cs.findbugs.ba.BasicBlock; import edu.umd.cs.findbugs.ba.DataflowAnalysisException; import edu.umd.cs.findbugs.ba.DepthFirstSearch; import edu.umd.cs.findbugs.ba.Edge; import edu.umd.cs.findbugs.ba.FrameDataflowAnalysis; import edu.umd.cs.findbugs.ba.Location; import edu.umd.cs.findbugs.ba.generic.GenericSignatureParser; import edu.umd.cs.findbugs.classfile.MethodDescriptor; import edu.umd.cs.findbugs.classfile.analysis.AnnotationValue; import edu.umd.cs.findbugs.classfile.analysis.MethodInfo; import edu.umd.cs.findbugs.io.IO; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; import org.apache.bcel.generic.InstructionHandle; import org.apache.bcel.generic.MethodGen; /** * Implements taint dataflow operations, in particular meeting facts, transfer * function is delegated to {@link TaintFrameModelingVisitor} * * @author David Formanek (Y Soft Corporation, a.s.) */ public class TaintAnalysis extends FrameDataflowAnalysis<Taint, TaintFrame> { private final MethodGen methodGen; private final MethodInfo methodDescriptor; private final TaintFrameModelingVisitor visitor; private int parameterStackSize; private List<Integer> slotToParameter; private static final List<String> TAINTED_ANNOTATIONS = loadFileContent( "taint-config/taint-param-annotations.txt" ); /** * Constructs analysis for the given method * * @param methodGen method to analyze * @param dfs DFS algorithm * @param descriptor descriptor of the method to analyze * @param taintConfig configured and derived taint summaries */ public TaintAnalysis(MethodGen methodGen, DepthFirstSearch dfs, MethodDescriptor descriptor, TaintConfig taintConfig) { super(dfs); this.methodGen = methodGen; this.methodDescriptor = (MethodInfo) descriptor; this.visitor = new TaintFrameModelingVisitor(methodGen.getConstantPool(), descriptor, taintConfig); computeParametersInfo(descriptor.getSignature(), descriptor.isStatic()); } @Override protected void mergeValues(TaintFrame frame, TaintFrame result, int i) throws DataflowAnalysisException { result.setValue(i, Taint.merge(result.getValue(i), frame.getValue(i))); } @Override public void transferInstruction(InstructionHandle handle, BasicBlock block, TaintFrame fact) throws DataflowAnalysisException { visitor.setFrameAndLocation(fact, new Location(handle, block)); visitor.analyzeInstruction(handle.getInstruction()); } @Override public TaintFrame createFact() { return new TaintFrame(methodGen.getMaxLocals()); } /** * Initialize the initial state of a TaintFrame. * @param fact Initial frame */ @Override public void initEntryFact(TaintFrame fact) { fact.setValid(); fact.clearStack(); boolean inMainMethod = isInMainMethod(); int numSlots = fact.getNumSlots(); int numLocals = fact.getNumLocals(); for (int i = 0; i < numSlots; ++i) { Taint value = new Taint(Taint.State.UNKNOWN); if (i < numLocals) { if (i < parameterStackSize) { if (isTaintedByAnnotation(i - 1)) { value = new Taint(Taint.State.TAINTED); // this would add line number for the first instruction in the method //value.addLocation(new TaintLocation(methodDescriptor, 0), true); } else if (inMainMethod) { if (FindSecBugsGlobalConfig.getInstance().isTaintedMainArgument()) { value = new Taint(Taint.State.TAINTED); } else { value = new Taint(Taint.State.SAFE); } } else { int stackOffset = parameterStackSize - i - 1; value.addParameter(stackOffset); } } value.setVariableIndex(i); } fact.setValue(i, value); } } /** * @return true if the method is the startup point of a console or gui application * ("public static void main(String[] args)"), false otherwise */ private boolean isInMainMethod() { return methodDescriptor.isStatic() && "main".equals(methodDescriptor.getName()) && "([Ljava/lang/String;)V".equals(methodDescriptor.getSignature()) && methodGen.getMethod().isPublic(); } /** * Determine if the slot value is tainted based on added framework annotations. * <hr/> * Example of mapping done:<br/> * * Method analyzed: "test(String,String,double)V" * For the slot no 2, it look at annotations on the parameter 2. * <pre> * 0 1 2 3 * +++++++++++++ * [ double param3 | double param3 | String param2 | String param1 ] * </pre> * <br/> * (Reminder : double and long take two slots for one parameter) * * @param slotNo * @return */ private boolean isTaintedByAnnotation(int slotNo) { if (slotNo >= 0 && methodDescriptor.hasParameterAnnotations()) { int parameter = slotToParameter.get(slotNo); Collection<AnnotationValue> annotations = methodDescriptor.getParameterAnnotations(parameter); for (AnnotationValue annotation : annotations) { if (TAINTED_ANNOTATIONS.contains(annotation.getAnnotationClass().getClassName())) { return true; } } } return false; } @Override public void meetInto(TaintFrame fact, Edge edge, TaintFrame result) throws DataflowAnalysisException { if (fact.isValid() && edge.isExceptionEdge()) { TaintFrame copy = null; // creates modifiable copy copy = modifyFrame(fact, copy); copy.clearStack(); // do not trust values that are safe just when an exception occurs copy.pushValue(new Taint(Taint.State.UNKNOWN)); fact = copy; } mergeInto(fact, result); } /** * This method must be called after executing the data flow */ public void finishAnalysis() { visitor.finishAnalysis(); } /** * Compute two values: * <li>The number of values required on the stack for a specific call.</li> * <li>The parameter association with each slot.</li> * * <hr/> * * For exemple : "(I[Ljava/lang/String;Ljava/lang/Object;)V" => void a(Integer, String, Object)<br/><br/> * Expected Stack prior the method invocation :<br/> * <pre> * +--------------+ * | 0: Object | * +--------------+ * | 1: String | * +--------------+ * | 2: int | * +--------------+ * </pre> * <hr/> * * Special note: double and long (primitive types) value take two slots.<br/> * For exemple : "(IDJ)V" => void a(Integer, Double, Long) * <pre> * +--------------+ * | 0: long pt1 | * +--------------+ * | 1: long pt2 | * +--------------+ * | 2: double pt1| * +--------------+ * | 3: double pt2| * +--------------+ * | 4: int | * +--------------+ * </pre> * * @param signature * @param isStatic * @return */ private void computeParametersInfo(String signature, boolean isStatic) { assert signature != null && !signature.isEmpty(); // static methods does not have reference to this int stackSize = isStatic ? 0 : 1; GenericSignatureParser parser = new GenericSignatureParser(signature); Iterator<String> iterator = parser.parameterSignatureIterator(); int paramIdx = 0; slotToParameter = new ArrayList<Integer>(); while (iterator.hasNext()) { String parameter = iterator.next(); if (parameter.equals("D") || parameter.equals("J")) { // double and long types takes two slots stackSize += 2; slotToParameter.add(paramIdx); slotToParameter.add(paramIdx); } else { stackSize++; slotToParameter.add(paramIdx); } paramIdx++; } parameterStackSize = stackSize; } private static List<String> loadFileContent(String path) { BufferedReader stream = null; try { InputStream in = TaintAnalysis.class.getClassLoader().getResourceAsStream(path); stream = new BufferedReader(new InputStreamReader(in, "utf-8")); String line; List<String> content = new ArrayList<String>(); while ((line = stream.readLine()) != null) { content.add(line.trim()); } return content; } catch (IOException ex) { assert false : ex.getMessage(); } finally { IO.close(stream); } return new ArrayList<String>(); } }