From 1cf643f131eb8d7d6f1288f40228a8e08f98ff50 Mon Sep 17 00:00:00 2001
From: David Malcolm <dmalcolm@redhat.com>
Date: Sun, 3 Mar 2019 11:49:47 -0500
Subject: [PATCH 41/87] FIXME: initial WIP of taint_state_machine

---
 gcc/analyzer/engine.cc                  |  28 ++++++
 gcc/analyzer/hardcoded-state-machine.cc | 153 ++++++++++++++++++++++++++++++++
 gcc/analyzer/hardcoded-state-machine.h  |  32 +++++++
 gcc/testsuite/gcc.dg/analyzer/taint-1.c |   3 +-
 4 files changed, 215 insertions(+), 1 deletion(-)

diff --git a/gcc/analyzer/engine.cc b/gcc/analyzer/engine.cc
index 4f49fc3..bc95b37 100644
--- a/gcc/analyzer/engine.cc
+++ b/gcc/analyzer/engine.cc
@@ -970,6 +970,28 @@ engine::on_stmt (const supernode *node, const gimple *stmt,
 				  t.is_true () ? NE_EXPR : EQ_EXPR,
 				  path_var (integer_zero_node, 0));
 	  }
+	  break;
+
+	case COMPONENT_REF:
+	  // TODO: propagate state
+	  {
+	    /* Copy the state of RHS to LHS.  */
+	    for (set_of_vars::iterator iter = old_sm_inst->m_active_vars.begin ();
+		 iter != old_sm_inst->m_active_vars.end ();
+		 ++iter)
+	      {
+		if ((*iter).m_var == TREE_OPERAND (rhs1, 0))
+		  {
+		    log ("got match for COMPONENT_REF");
+		    engine_sm_context ctxt (this, sm_inst, old_sm_inst, NULL /* FIXME */,
+					    ps);
+		    // FIXME: we want to overwrite any old states
+		    // (but respect the gstate, I think):
+		    ctxt.on_transition (node, stmt, lhs, 0/* FIXME */, (*iter).m_state);
+		  }
+	      }
+	  }
+	  break;
 	}
     }
   if (const greturn *return_ = dyn_cast <const greturn *> (stmt))
@@ -1668,6 +1690,12 @@ run_checkers ()
       run_one_checker (sg, sm, the_logger.get_logger ());
     }
 
+    {
+      auto_client_timevar tv ("taint_state_machine");
+      taint_state_machine sm (the_logger.get_logger ());
+      run_one_checker (sg, sm, the_logger.get_logger ());
+    }
+
     /* end of lifetime of the_logger (so that dump file is closed after the
        various dtors run).  */
   }
diff --git a/gcc/analyzer/hardcoded-state-machine.cc b/gcc/analyzer/hardcoded-state-machine.cc
index 6da6b56..f06916f 100644
--- a/gcc/analyzer/hardcoded-state-machine.cc
+++ b/gcc/analyzer/hardcoded-state-machine.cc
@@ -375,9 +375,162 @@ fileptr_state_machine::on_leak (sm_context *sm_ctxt,
 
 ////////////////////////////////////////////////////////////////////////////
 
+taint_state_machine::taint_state_machine (logger *logger)
+: state_machine ("taint", logger)
+{
+  // TODO: parse all of this from a file:
+  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");
+}
+
+void
+taint_state_machine::on_stmt (sm_context *sm_ctxt,
+			       const supernode *node,
+			       const gimple *stmt) const
+{
+  //tree fndecl = node->m_fun->decl;
+
+  // FIXME: do all of this from data
+  if (const gcall *call = dyn_cast <const gcall *> (stmt))
+    {
+      if (is_named_call_p (call, "fread", 4))
+	{
+	  if (DEBUG)
+	    inform (stmt->location, "DBG: got taint");
+	  tree arg = gimple_call_arg (call, 0);
+	  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);
+	}
+    }
+
+#if 0
+  /* FIXME: It's not clear if we need this, due to the SSA representation:
+     each assignment creates a new SSA_NAME.  */
+  if (tree lhs = is_zero_assignment (stmt))
+    {
+      if (any_pointer_p (lhs))
+	{
+	  sm_ctxt->on_transition (node, stmt, lhs, m_start, m_null);
+	  sm_ctxt->on_transition (node, stmt, lhs, m_unchecked, m_null);
+	  sm_ctxt->on_transition (node, stmt, lhs, m_nonnull, m_null);
+	  sm_ctxt->on_transition (node, stmt, lhs, m_free, m_null);
+	}
+    }
+#endif
+
+  if (const gassign *assign = dyn_cast <const gassign *> (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);
+	  sm_ctxt->error (node, stmt, arg, m_tainted,
+			  "use of tainted value %qE in array lookup without bounds checking",
+			  arg);
+	  sm_ctxt->on_transition (node, stmt, arg, m_tainted, m_stop);
+	}
+    }
+
+#if 0
+  // TODO: dereferences
+  for (unsigned i = 0; i < gimple_num_ops (stmt); i++)
+    {
+      tree op = gimple_op (stmt, i);
+#if 0
+      if (op)
+	inform (stmt->location, "op %i: %qE", i, op);
+      else
+	inform (stmt->location, "op %i: NULL_TREE", i);
+#endif
+      if (!op)
+	continue;
+      if (TREE_CODE (op) == MEM_REF)
+	{
+	  tree arg = TREE_OPERAND (op, 0);
+	  sm_ctxt->error (node, stmt, arg, m_unchecked, "use of possibly-NULL %qE", arg);
+	  sm_ctxt->on_transition (node, stmt, arg, m_unchecked, m_nonnull);
+
+	  sm_ctxt->error (node, stmt, arg, m_null, "use of NULL %qE", arg);
+	  sm_ctxt->on_transition (node, stmt, arg, m_null, m_stop);
+
+	  sm_ctxt->error (node, stmt, arg, m_free, "use after free of %qE", arg);
+	  sm_ctxt->on_transition (node, stmt, arg, m_free, m_stop);
+	}
+    }
+#endif
+}
+
+/* FIXME.  */
+
+void
+taint_state_machine::on_edge (sm_context *sm_ctxt,
+			       const gimple *last_stmt,
+			       const superedge *edge) const
+{
+  if (last_stmt == NULL)
+    return;
+
+  if (const gcond *cond_stmt = dyn_cast <const gcond *> (last_stmt))
+    {
+#if 0
+      tree arg;
+      enum tree_code op;
+      if (is_comparison_against_zero (cond_stmt, &arg, &op))
+	{
+	  if (!any_pointer_p (arg))
+	    return;
+
+	  ::edge cfg_edge = edge->get_any_cfg_edge ();
+	  gcc_assert (cfg_edge != NULL);
+	  gcc_assert (cfg_edge->flags & (EDGE_TRUE_VALUE | EDGE_FALSE_VALUE));
+	  
+	  if (op == NE_EXPR)
+	    {
+	      log ("got 'ARG != 0' match");
+	      sm_ctxt->on_transition (edge->m_src, last_stmt, arg, m_unchecked,
+				      (cfg_edge->flags & EDGE_TRUE_VALUE
+				       ? m_nonnull : m_null));
+	    }
+	  else if (op == EQ_EXPR)
+	    {
+	      log ("got 'ARG == 0' match");
+	      sm_ctxt->on_transition (edge->m_src, last_stmt, arg, m_unchecked,
+				      (cfg_edge->flags & EDGE_TRUE_VALUE
+				       ? m_null : m_nonnull));
+	    }
+	}
+#endif
+    }
+
+  // TODO: something similar for switch statements?
+}
+
+void
+taint_state_machine::on_leak (sm_context *sm_ctxt,
+			       const supernode *node,
+			       const gimple *stmt,
+			       tree var,
+			       state_machine::state_t state) const
+{
+  /* Empty.  */
+}
+
+////////////////////////////////////////////////////////////////////////////
+
 void
 make_checkers (auto_delete_vec <state_machine> &out, logger *logger)
 {
   out.safe_push (new malloc_state_machine (logger));
   out.safe_push (new fileptr_state_machine (logger));
+  out.safe_push (new taint_state_machine (logger));
 }
diff --git a/gcc/analyzer/hardcoded-state-machine.h b/gcc/analyzer/hardcoded-state-machine.h
index 369c6c3..a4bc713 100644
--- a/gcc/analyzer/hardcoded-state-machine.h
+++ b/gcc/analyzer/hardcoded-state-machine.h
@@ -144,3 +144,35 @@ private:
   state_t m_closed;
   state_t m_stop;
 };
+
+////////////////////////////////////////////////////////////////////////////
+
+/* A particular state machine: tainted data, for
+   prototyping purposes.  */
+
+class taint_state_machine : public state_machine
+{
+public:
+  taint_state_machine (logger *logger);
+
+  void on_stmt (sm_context *sm_ctxt,
+		const supernode *node,
+		const gimple *stmt) const FINAL OVERRIDE;
+
+  void on_edge (sm_context *sm_ctxt,
+		const gimple *last_stmt,
+		const superedge *edge) 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;
+
+private:
+  state_t m_start;
+  state_t m_tainted;
+  state_t m_has_lb;
+  state_t m_has_ub;
+  state_t m_stop;
+};
diff --git a/gcc/testsuite/gcc.dg/analyzer/taint-1.c b/gcc/testsuite/gcc.dg/analyzer/taint-1.c
index a085761..257284f 100644
--- a/gcc/testsuite/gcc.dg/analyzer/taint-1.c
+++ b/gcc/testsuite/gcc.dg/analyzer/taint-1.c
@@ -15,7 +15,8 @@ char test_1(FILE *f)
   if (1 == fread(&tmp, sizeof(tmp), 1, f)) {
     /* BUG: the following array lookup trusts that the input data's index is
        in the range 0 <= i < 256; otherwise it's accessing the stack */
-    return tmp.buf[tmp.i];
+    return tmp.buf[tmp.i]; // { dg-error "use of tainted value '<unknown>' in array lookup without bounds checking" }
+    // TODO: better description of value
   }
   return 0;
 }
-- 
1.8.5.3

