All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.h3xstream.findsecbugs.taintanalysis.Taint Maven / Gradle / Ivy

Go to download

Core module of the project. It include all the FindBugs detectors. The resulting jar is the published plugin.

There is a newer version: 1.13.0
Show newest version
/**
 * 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