com.h3xstream.findsecbugs.taintanalysis.Taint Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of findsecbugs-plugin Show documentation
Show all versions of findsecbugs-plugin Show documentation
Core module of the project. It include all the FindBugs detectors.
The resulting jar is the published plugin.
/**
* 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.AnalysisContext;
import edu.umd.cs.findbugs.util.ClassName;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import org.apache.bcel.generic.ObjectType;
/**
* Representation of taint dataflow facts (dataflow values) for each slot in
* {@link TaintFrame}
*
* @author David Formanek (Y Soft Corporation, a.s.)
*/
public class Taint {
public enum State {
TAINTED(false, true, false),
UNKNOWN(false, false, true),
SAFE(true, false, false),
NULL(true, false, false),
INVALID(false, false, false);
private final boolean isSafe;
private final boolean isTainted;
private final boolean isUnknown;
State(boolean isSafe, boolean isTainted, boolean isUnknown) {
this.isSafe = isSafe;
this.isTainted = isTainted;
this.isUnknown = isUnknown;
}
/**
* Returns the "more dangerous" state (TAINTED > UNKNOWN > SAFE
* > NULL > INVALID) as a merge of two states
*
* @param a first state to merge
* @param b second state to merge
* @return one of the values a, b
*/
public static State merge(State a, State b) {
if (a == null || b == null) {
throw new NullPointerException(
"use Taint.State." + INVALID.name() + " instead of null"
);
}
if (a == TAINTED || b == TAINTED) {
return TAINTED;
}
if (a == UNKNOWN || b == UNKNOWN) {
return UNKNOWN;
}
if (a == SAFE || b == SAFE) {
return SAFE;
}
if (a == NULL || b == NULL) {
return NULL;
}
assert a == INVALID && b == INVALID;
return INVALID;
}
}
public enum Tag {
XSS_SAFE,
SQL_INJECTION_SAFE,
COMMAND_INJECTION_SAFE,
LDAP_INJECTION_SAFE,
XPATH_INJECTION_SAFE,
CR_ENCODED,
LF_ENCODED,
QUOTE_ENCODED,
APOSTROPHE_ENCODED,
LT_ENCODED
}
private State state;
private static final int INVALID_INDEX = -1;
private int variableIndex;
private final Set taintLocations;
private final Set unknownLocations;
private final Set parameters;
private State nonParametricState;
private ObjectType realInstanceClass;
private final Set tags;
private final Set tagsToRemove;
private String constantValue;
private String debugInfo = null;
/**
* Constructs a new empty instance of Taint with the specified state
*
* @param state state of the fact
* @throws NullPointerException if argument is null
* @throws IllegalArgumentException if argument is INVALID
*/
public Taint(State state) {
Objects.requireNonNull(state, "state is null");
if (state == State.INVALID) {
throw new IllegalArgumentException("state not allowed");
}
this.state = state;
this.variableIndex = INVALID_INDEX;
this.unknownLocations = new HashSet();
this.taintLocations = new HashSet();
this.parameters = new HashSet();
this.nonParametricState = State.INVALID;
this.realInstanceClass = null;
this.tags = EnumSet.noneOf(Tag.class);
this.tagsToRemove = EnumSet.noneOf(Tag.class);
this.constantValue = null;
if (FindSecBugsGlobalConfig.getInstance().isDebugTaintState()) {
this.debugInfo = "?";
}
}
/**
* Creates a hard copy of the specified Taint instance
*
* @param taint instance to copy
* @throws NullPointerException if argument is null
*/
public Taint(Taint taint) {
Objects.requireNonNull(taint, "taint is null");
assert taint.state != null;
this.state = taint.state;
this.variableIndex = taint.variableIndex;
this.taintLocations = new HashSet(taint.taintLocations);
this.unknownLocations = new HashSet(taint.unknownLocations);
this.parameters = new HashSet(taint.getParameters());
this.nonParametricState = taint.nonParametricState;
this.realInstanceClass = taint.realInstanceClass;
this.tags = EnumSet.copyOf(taint.tags);
this.tagsToRemove = EnumSet.copyOf(taint.tagsToRemove);
this.constantValue = taint.constantValue;
if (FindSecBugsGlobalConfig.getInstance().isDebugTaintState()) {
this.debugInfo = taint.debugInfo;
}
}
/**
* Returns the taint state of this fact
*
* @return taint state
*/
public State getState() {
assert state != null && state != State.INVALID;
return state;
}
void setState(State state) {
Objects.requireNonNull(state, "state is null");
if (state == State.INVALID) {
throw new IllegalArgumentException("state not allowed to be set");
}
this.state = state;
}
/**
* If known (check first), returns the index of the local variable,
* where the value matching this fact is stored
*
* @return the index in the frame
* @throws IllegalStateException if index is uknown
*/
public int getVariableIndex() {
if (variableIndex == INVALID_INDEX) {
throw new IllegalStateException("index not set or has been invalidated");
}
assert variableIndex >= 0;
return variableIndex;
}
/**
* Checks if the index of the local variable matching this fact is known
*
* @return true if index is known, false otherwise
*/
public boolean hasValidVariableIndex() {
return variableIndex != INVALID_INDEX;
}
void setVariableIndex(int index) {
if (index < 0) {
throw new IllegalArgumentException("negative index");
}
variableIndex = index;
}
void invalidateVariableIndex() {
variableIndex = INVALID_INDEX;
}
/**
* Adds location for a taint source or path to remember for reporting
*
* @param location location to remember
* @param isKnownTaintSource true for tainted value, false if just not safe
* @throws NullPointerException if location is null
*/
public void addLocation(TaintLocation location, boolean isKnownTaintSource) {
Objects.requireNonNull(location, "location is null");
if (isKnownTaintSource) {
taintLocations.add(location);
} else {
unknownLocations.add(location);
}
}
/**
* Returns locations with taint sources or nodes on path from those
* sources, if there are some locations confirmed to be tainted,
* only those are returned
*
* @return unmodifiable set of locations
*/
public Set getLocations() {
if (taintLocations.isEmpty()) {
return Collections.unmodifiableSet(unknownLocations);
}
return Collections.unmodifiableSet(taintLocations);
}
/**
* Checks whether values matching this fact are always trusted
*
* @return true if the taint state is safe (or null), false otherwise
*/
public boolean isSafe() {
return state.isSafe;
}
/**
* Checks whether values matching this fact are probably untrusted
*
* @return true for the state TAINTED, false otherwise
*/
public boolean isTainted() {
return state.isTainted;
}
/**
* Checks whether values matching this fact can be untrusted but also safe
*
* @return true for the state UNKNOWN, false otherwise
*/
public boolean isUnknown() {
return state.isUnknown;
}
void addParameter(int parameterIndex) {
if (parameterIndex < 0) {
throw new IllegalArgumentException("index cannot be negative");
}
parameters.add(parameterIndex);
}
/**
* Checks if the taint state of this fact depends on the method arguments
*
* @return true if there is an influence, false otherwise
*/
public boolean hasParameters() {
return !parameters.isEmpty();
}
/**
* Returns the method arguments influencing the taint state of this fact
*
* @return unmodifiable set of parameter indices
*/
public Set getParameters() {
return Collections.unmodifiableSet(parameters);
}
/**
* Gets the state influencing the state of this fact if dependant on method
* arguments, final state is given by merge of that state and arguments
*
* @return
*/
public State getNonParametricState() {
return nonParametricState;
}
void setNonParametricState(State state) {
Objects.requireNonNull(state, "state is null");
if (state == State.INVALID) {
throw new IllegalArgumentException("state not allowed to be set");
}
nonParametricState = state;
}
/**
* Finds out the real type of instance matching this fact if possible
*
* @return type of the instance or null if uknown
*/
public ObjectType getRealInstanceClass() {
return realInstanceClass;
}
void setRealInstanceClass(ObjectType objectType) {
// can be null
realInstanceClass = objectType;
}
/**
* Finds out the real class name of instance matching this fact if possible
*
* @return class name of the instance or null if uknown
*/
public String getRealInstanceClassName() {
if (realInstanceClass == null) {
return null;
}
return ClassName.toSlashedClassName(realInstanceClass.getClassName());
}
/**
* Adds the specified taint tag to this fact or marks this tag to add
* if this fact acts like a derivation of taint transfer behaviour
*
* @param tag tag to add
* @return true if this tag was not present before, false otherwise
*/
public boolean addTag(Tag tag) {
return tags.add(tag);
}
/**
* Checks whether the specified taint tag is present for this fact
*
* @param tag tag to check
* @return true if it is present, false otherwise
*/
public boolean hasTag(Tag tag) {
return tags.contains(tag);
}
/**
* Checks if there are any taint tags for this fact
*
* @return true if number of tags is > 0, false otherwise
*/
public boolean hasTags() {
return !tags.isEmpty();
}
/**
* Returns all present taint tags for this fact
*
* @return unmodifiable set of all present taint tags
*/
public Set getTags() {
return Collections.unmodifiableSet(tags);
}
/**
* Removes the specified tag (if present) or marks this tag to remove
* if this fact acts like a derivation of taint transfer behaviour
*
* @param tag tag to remove
* @return true if the tag was present, false otherwise
*/
public boolean removeTag(Tag tag) {
tagsToRemove.add(tag);
return tags.remove(tag);
}
/**
* Checks if there are some tags to remove
* (if this fact acts like a taint derivation spec.)
*
* @return true if there are some, false otherwise
*/
public boolean isRemovingTags() {
return !tagsToRemove.isEmpty();
}
/**
* Returns tags to remove (if this fact acts like a taint derivation spec.)
*
* @return unmodifiable set of tags
*/
public Set getTagsToRemove() {
return Collections.unmodifiableSet(tagsToRemove);
}
/**
* Returns the constant value of the string or char if known
*
* @return constant value or null if unknown
*/
public String getConstantValue() {
return constantValue;
}
void setConstantValue(String value) {
this.constantValue = value;
}
/**
* Constructs a new instance of taint from the specified state name
*
* @param stateName name of the state
* @return the constructed instance
* @throws IllegalArgumentException if the name does not match any state
*/
public static Taint valueOf(String stateName) {
// exceptions thrown from Enum.valueOf
return valueOf(State.valueOf(stateName));
}
/**
* Constructs a new instance of taint from the specified state
*
* @param state the specified state
* @return the constructed instance
* @throws NullPointerException if state is null
*/
public static Taint valueOf(State state) {
Objects.requireNonNull(state, "state is null");
if (state == State.INVALID) {
return null;
}
return new Taint(state);
}
/**
* Returns the merge of the facts such that it can represent any of them
*
* @param a first state to merge
* @param b second state to merge
* @return constructed merge of the specified facts
*/
public static Taint merge(Taint a, Taint b) {
if (a == null) {
if (b == null) {
return null;
} else {
return new Taint(b);
}
} else if (b == null) {
return new Taint(a);
}
assert a != null && b != null;
Taint result = new Taint(State.merge(a.getState(), b.getState()));
if (a.variableIndex == b.variableIndex) {
result.variableIndex = a.variableIndex;
}
result.taintLocations.addAll(a.taintLocations);
result.taintLocations.addAll(b.taintLocations);
result.unknownLocations.addAll(a.unknownLocations);
result.unknownLocations.addAll(b.unknownLocations);
if (!result.isTainted()) {
mergeParameters(a, b, result);
}
mergeRealInstanceClass(a, b, result);
mergeTags(a, b, result);
if (a.constantValue != null && a.constantValue.equals(b.constantValue)) {
result.constantValue = a.constantValue;
}
if (FindSecBugsGlobalConfig.getInstance().isDebugTaintState()) {
result.setDebugInfo("[" + a.getDebugInfo() + "]+[" + b.getDebugInfo() + "]");
}
assert !result.hasParameters() || result.isUnknown();
return result;
}
private static void mergeParameters(Taint a, Taint b, Taint result) {
result.parameters.addAll(a.parameters);
result.parameters.addAll(b.parameters);
if (a.hasParameters()) {
if (b.hasParameters()) {
result.nonParametricState = State.merge(a.nonParametricState, b.nonParametricState);
} else {
result.nonParametricState = State.merge(b.state, a.nonParametricState);
}
} else {
if (b.hasParameters()) {
result.nonParametricState = State.merge(a.state, b.nonParametricState);
}
}
}
private static void mergeRealInstanceClass(Taint a, Taint b, Taint result) {
if (a.realInstanceClass != null && b.realInstanceClass != null) {
try {
if (a.realInstanceClass.equals(b.realInstanceClass)
|| b.realInstanceClass.subclassOf(a.realInstanceClass)) {
result.realInstanceClass = a.realInstanceClass;
} else if (a.realInstanceClass.subclassOf(b.realInstanceClass)) {
result.realInstanceClass = b.realInstanceClass;
}
} catch (ClassNotFoundException ex) {
AnalysisContext.reportMissingClass(ex);
}
}
}
private static void mergeTags(Taint a, Taint b, Taint result) {
if (a.isSafe()) {
result.tags.addAll(b.tags);
} else if (b.isSafe()) {
result.tags.addAll(a.tags);
} else {
result.tags.addAll(a.tags);
result.tags.retainAll(b.tags);
}
result.tagsToRemove.addAll(a.tagsToRemove);
result.tagsToRemove.addAll(b.tagsToRemove);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!(obj instanceof Taint)) {
return false;
}
Taint other = (Taint) obj;
return this.state == other.state
&& this.variableIndex == other.variableIndex
&& this.taintLocations.equals(other.taintLocations)
&& this.unknownLocations.equals(other.unknownLocations)
&& this.parameters.equals(other.parameters)
&& this.nonParametricState == other.nonParametricState
&& Objects.equals(this.realInstanceClass, other.realInstanceClass)
&& this.tags.equals(other.tags)
&& Objects.equals(this.constantValue, other.constantValue);
}
@Override
public int hashCode() {
return Objects.hash(state, variableIndex, taintLocations, unknownLocations,
parameters, nonParametricState, realInstanceClass, tags, constantValue);
}
/**
* Gets the info for debugging merged from all used facts
*
* @return previousle set info
*/
public String getDebugInfo() {
return debugInfo;
}
/**
* Sets info for debugging purposes (consumes much memory)
*
* @param debugInfo info to store
* @return the modified instance itself
*/
public Taint setDebugInfo(String debugInfo) {
this.debugInfo = debugInfo;
return this;
}
@Override
public String toString() {
assert state != null;
StringBuilder sb = new StringBuilder(state.name().substring(0, 1));
if (hasValidVariableIndex()) {
sb.append(variableIndex);
}
if (!parameters.isEmpty()) {
sb.append(parameters);
}
assert nonParametricState != null;
if (nonParametricState != State.INVALID) {
sb.append('(').append(nonParametricState.name().substring(0, 1)).append(')');
}
if (debugInfo != null) {
sb.append(" {").append(debugInfo).append('}');
}
if (tags.size() > 0) {
sb.append(" tags: ").append(Arrays.toString(tags.toArray()));
}
return sb.toString();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy