From 273b44f963335d1e1ba9d3b1ba3f48a10101c94e Mon Sep 17 00:00:00 2001 From: David Malcolm Date: Fri, 27 Sep 2019 09:30:52 -0400 Subject: [PATCH 47/60] {HAS FIXMES} analyzer: new file: sm-taint.cc This patch adds a state machine checker for tracking "taint", where data potentially under an attacker's control is used for things like array indices without sanitization (CWE-129). This checker isn't ready for production, and is presented as a proof-of-concept of the sm-based approach. gcc/ChangeLog: * analyzer/sm-taint.cc: New file. --- gcc/analyzer/sm-taint.cc | 356 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 356 insertions(+) create mode 100644 gcc/analyzer/sm-taint.cc diff --git a/gcc/analyzer/sm-taint.cc b/gcc/analyzer/sm-taint.cc new file mode 100644 index 0000000..edd7990 --- /dev/null +++ b/gcc/analyzer/sm-taint.cc @@ -0,0 +1,356 @@ +/* An experimental state machine, for tracking "taint": unsanitized uses + of data potentially under an attacker's control. + + Copyright (C) 2019 Free Software Foundation, Inc. + Contributed by David Malcolm . + +This file is part of GCC. + +GCC is free software; you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 3, or (at your option) +any later version. + +GCC 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GCC; see the file COPYING3. If not see +. */ +/* FIXME. */ +#include "config.h" +#include "gcc-plugin.h" +#include "system.h" +#include "coretypes.h" +#include "tree.h" +#include "tm.h" +#include "toplev.h" +#include "hash-table.h" +#include "vec.h" +#include "ggc.h" +#include "basic-block.h" +#include "tree-ssa-alias.h" +#include "internal-fn.h" +#include "gimple-fold.h" +#include "tree-eh.h" +#include "gimple-expr.h" +#include "is-a.h" +#include "gimple.h" +#include "tree-pass.h" +#include "intl.h" +#include "context.h" +#include "diagnostic.h" +#include "bitmap.h" +#include "gimple.h" +#include "gimple-iterator.h" +#include "gimple-pretty-print.h" +#include "tree-pretty-print.h" +#include "analyzer/analyzer.h" +#include "analyzer/graphviz.h" +#include "cgraph.h" +#include "diagnostic-path.h" +#include "gcc-rich-location.h" +#include +#include "analyzer/supergraph.h" +#include "analyzer/sm.h" +#include "analyzer/pending-diagnostic.h" + +static const bool DEBUG = false; +//static const bool DEBUG = true; + +/* An experimental state machine, for tracking "taint": unsanitized uses + of data potentially under an attacker's control. */ + +class taint_state_machine : public state_machine +{ +public: + taint_state_machine (logger *logger); + + bool inherited_state_p () const FINAL OVERRIDE { return true; } + + bool on_stmt (sm_context *sm_ctxt, + const supernode *node, + const gimple *stmt) const FINAL OVERRIDE; + + void on_condition (sm_context *sm_ctxt, + const supernode *node, + const gimple *stmt, + tree lhs, + enum tree_code op, + tree rhs) const FINAL OVERRIDE; + + void on_leak (sm_context *sm_ctxt, + const supernode *node, + const gimple *stmt, + tree var, + state_machine::state_t state) const FINAL OVERRIDE; + bool can_purge_p (state_t s) const FINAL OVERRIDE; + + state_t m_start; + state_t m_tainted; + state_t m_has_lb; + state_t m_has_ub; + state_t m_stop; +}; + +//////////////////////////////////////////////////////////////////////////// + +enum bounds +{ + BOUNDS_NONE, + BOUNDS_UPPER, + BOUNDS_LOWER, +}; + +class tainted_array_index + : public pending_diagnostic_subclass +{ +public: + tainted_array_index (const taint_state_machine &sm, tree arg, + enum bounds has_bounds) + : m_sm (sm), m_arg (arg), m_has_bounds (has_bounds) {} + + const char *get_kind () const FINAL OVERRIDE { return "tainted_array_index"; } + + bool operator== (const tainted_array_index &other) const + { + return m_arg == other.m_arg; + } + + void emit (rich_location *rich_loc) FINAL OVERRIDE + { + metadata m; + m.add_cwe (129); + switch (m_has_bounds) + { + default: + gcc_unreachable (); + case BOUNDS_NONE: + warning_at (rich_loc, m, OPT_Wanalyzer_tainted_array_index, + "use of tainted value %qE in array lookup" + " without bounds checking", + m_arg); + break; + case BOUNDS_UPPER: + warning_at (rich_loc, m, OPT_Wanalyzer_tainted_array_index, + "use of tainted value %qE in array lookup" + " without lower-bounds checking", + m_arg); + break; + case BOUNDS_LOWER: + warning_at (rich_loc, m, OPT_Wanalyzer_tainted_array_index, + "use of tainted value %qE in array lookup" + " without upper-bounds checking", + m_arg); + break; + } + } + + label_text describe_state_change (const evdesc::state_change &change) + FINAL OVERRIDE + { + if (change.m_new_state == m_sm.m_tainted) + { + if (change.m_origin) + return change.formatted_print ("%qE has an unchecked value here" + " (from %qE)", + change.m_expr, change.m_origin); + else + return change.formatted_print ("%qE gets an unchecked value here", + change.m_expr); + } + else if (change.m_new_state == m_sm.m_has_lb) + return change.formatted_print ("%qE has its lower bound checked here", + change.m_expr); + else if (change.m_new_state == m_sm.m_has_ub) + return change.formatted_print ("%qE has its upper bound checked here", + change.m_expr); + return label_text (); + } + + label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE + { + switch (m_has_bounds) + { + default: + gcc_unreachable (); + case BOUNDS_NONE: + return ev.formatted_print ("use of tainted value %qE in array lookup" + " without bounds checking", + m_arg); + case BOUNDS_UPPER: + return ev.formatted_print ("use of tainted value %qE in array lookup" + " without lower-bounds checking", + m_arg); + case BOUNDS_LOWER: + return ev.formatted_print ("use of tainted value %qE in array lookup" + " without upper-bounds checking", + m_arg); + } + } + +private: + const taint_state_machine &m_sm; + tree m_arg; + enum bounds m_has_bounds; +}; + +//////////////////////////////////////////////////////////////////////////// + +/* FIXME. */ + +taint_state_machine::taint_state_machine (logger *logger) +: state_machine ("taint", logger) +{ + m_start = add_state ("start"); + m_tainted = add_state ("tainted"); + m_has_lb = add_state ("has_lb"); + m_has_ub = add_state ("has_ub"); + m_stop = add_state ("stop"); +} + +/* FIXME. */ + +bool +taint_state_machine::on_stmt (sm_context *sm_ctxt, + const supernode *node, + const gimple *stmt) const +{ + //tree fndecl = node->m_fun->decl; + + if (const gcall *call = dyn_cast (stmt)) + { + if (is_named_call_p (call, "fread", 4)) + { + if (DEBUG) + inform (stmt->location, "DBG: got taint"); + tree arg = gimple_call_arg (call, 0); + arg = sm_ctxt->get_readable_tree (arg); + + sm_ctxt->on_transition (node, stmt, arg, m_start, m_tainted); + + // FIXME: dereference an ADDR_EXPR: + // FIXME: should the engine do this? + if (TREE_CODE (arg) == ADDR_EXPR) + sm_ctxt->on_transition (node, stmt, TREE_OPERAND (arg, 0), + m_start, m_tainted); + return true; + } + } + + if (const gassign *assign = dyn_cast (stmt)) + { + //tree lhs = gimple_assign_lhs (assign); + tree rhs1 = gimple_assign_rhs1 (assign); + enum tree_code op = gimple_assign_rhs_code (assign); + if (op == ARRAY_REF) + { + tree arg = TREE_OPERAND (rhs1, 1); + arg = sm_ctxt->get_readable_tree (arg); + + /* Unsigned types have an implicit lower bound. */ + bool is_unsigned = false; + if (INTEGRAL_TYPE_P (TREE_TYPE (arg))) + is_unsigned = TYPE_UNSIGNED (TREE_TYPE (arg)); + + /* Complain about missing bounds. */ + sm_ctxt->warn_for_state + (node, stmt, arg, m_tainted, + new tainted_array_index (*this, arg, + is_unsigned + ? BOUNDS_LOWER : BOUNDS_NONE)); + sm_ctxt->on_transition (node, stmt, arg, m_tainted, m_stop); + + /* Complain about missing upper bound. */ + sm_ctxt->warn_for_state (node, stmt, arg, m_has_lb, + new tainted_array_index (*this, arg, + BOUNDS_LOWER)); + sm_ctxt->on_transition (node, stmt, arg, m_has_lb, m_stop); + + /* Complain about missing lower bound. */ + if (!is_unsigned) + { + sm_ctxt->warn_for_state (node, stmt, arg, m_has_ub, + new tainted_array_index (*this, arg, + BOUNDS_UPPER)); + sm_ctxt->on_transition (node, stmt, arg, m_has_ub, m_stop); + } + } + } + + return false; +} + +/* FIXME. */ + +void +taint_state_machine::on_condition (sm_context *sm_ctxt, + const supernode *node, + const gimple *stmt, + tree lhs, + enum tree_code op, + tree rhs ATTRIBUTE_UNUSED) const +{ + if (stmt == NULL) + return; + + // TODO: this doesn't use the RHS; should we make it symmetric? + + // TODO + switch (op) + { + //case NE_EXPR: + //case EQ_EXPR: + case GE_EXPR: + case GT_EXPR: + { + sm_ctxt->on_transition (node, stmt, lhs, m_tainted, + m_has_lb); + sm_ctxt->on_transition (node, stmt, lhs, m_has_ub, + m_stop); + } + break; + case LE_EXPR: + case LT_EXPR: + { + sm_ctxt->on_transition (node, stmt, lhs, m_tainted, + m_has_ub); + sm_ctxt->on_transition (node, stmt, lhs, m_has_lb, + m_stop); + } + break; + default: + break; + } +} + +/* FIXME. */ + +void +taint_state_machine::on_leak (sm_context *sm_ctxt ATTRIBUTE_UNUSED, + const supernode *node ATTRIBUTE_UNUSED, + const gimple *stmt ATTRIBUTE_UNUSED, + tree var ATTRIBUTE_UNUSED, + state_machine::state_t state ATTRIBUTE_UNUSED) + const +{ + /* Empty. */ +} + +/* FIXME. */ + +bool +taint_state_machine::can_purge_p (state_t s ATTRIBUTE_UNUSED) const +{ + return true; +} + +/* FIXME. */ + +state_machine * +make_taint_state_machine (logger *logger) +{ + return new taint_state_machine (logger); +} -- 1.8.5.3