diff mbox series

[committed] analyzer; reset sm-state for SSA names at def-stmts [PR93695,PR99044,PR99716]

Message ID 20210325005114.3839305-1-dmalcolm@redhat.com
State New
Headers show
Series [committed] analyzer; reset sm-state for SSA names at def-stmts [PR93695,PR99044,PR99716] | expand

Commit Message

David Malcolm March 25, 2021, 12:51 a.m. UTC
Various false positives from -fanalyzer involve SSA names in loops,
where sm-state associated with an SSA name from one iteration is
erroneously reused in a subsequent iteration.

For example, PR analyzer/99716 describes a false
  "double 'fclose' of FILE 'fp'"
on:

  for (i = 0; i < 2; ++i) {
    FILE *fp = fopen ("/tmp/test", "w");
    fprintf (fp, "hello");
    fclose (fp);
  }

where the gimple of the loop body is:

  fp_7 = fopen ("/tmp/test", "w");
  __builtin_fwrite ("hello", 1, 5, fp_7);
  fclose (fp_7);
  i_10 = i_1 + 1;

where fp_7 transitions to "closed" at the fclose, but is not
reset at the subsequent fopen, leading to the false positive
when the fclose is re-reached.

The fix is to reset sm-state for svalues that involve an SSA name
at the SSA name's def-stmt, since the def-stmt effectively changes
the meaning of those related svalues.

Successfully bootstrapped & regrtested on x86_64-pc-linux-gnu.
Pushed to trunk as r11-7820-g71fc4655ab86ab66b40165b2cb49c1395ca82a9a.

gcc/analyzer/ChangeLog:
	PR analyzer/93695
	PR analyzer/99044
	PR analyzer/99716
	* engine.cc (exploded_node::on_stmt): Clear sm-state involving
	an SSA name at the def-stmt of that SSA name.
	* program-state.cc (sm_state_map::purge_state_involving): New.
	* program-state.h (sm_state_map::purge_state_involving): New decl.
	* region-model.cc (selftest::test_involves_p): New.
	(selftest::analyzer_region_model_cc_tests): Call it.
	* svalue.cc (class involvement_visitor): New class
	(svalue::involves_p): New.
	* svalue.h (svalue::involves_p): New decl.

gcc/testsuite/ChangeLog:
	PR analyzer/93695
	PR analyzer/99044
	PR analyzer/99716
	* gcc.dg/analyzer/attr-malloc-CVE-2019-19078-usb-leak.c: Remove
	xfail.
	* gcc.dg/analyzer/pr93695-1.c: New test.
	* gcc.dg/analyzer/pr99044-1.c: New test.
	* gcc.dg/analyzer/pr99044-2.c: New test.
	* gcc.dg/analyzer/pr99716-1.c: New test.
	* gcc.dg/analyzer/pr99716-2.c: New test.
	* gcc.dg/analyzer/pr99716-3.c: New test.
---
 gcc/analyzer/engine.cc                        | 25 ++++++++
 gcc/analyzer/program-state.cc                 | 30 ++++++++++
 gcc/analyzer/program-state.h                  |  3 +
 gcc/analyzer/region-model.cc                  | 32 ++++++++++
 gcc/analyzer/svalue.cc                        | 34 +++++++++++
 gcc/analyzer/svalue.h                         |  2 +
 .../attr-malloc-CVE-2019-19078-usb-leak.c     |  4 +-
 gcc/testsuite/gcc.dg/analyzer/pr93695-1.c     | 53 ++++++++++++++++
 gcc/testsuite/gcc.dg/analyzer/pr99044-1.c     | 60 +++++++++++++++++++
 gcc/testsuite/gcc.dg/analyzer/pr99044-2.c     | 42 +++++++++++++
 gcc/testsuite/gcc.dg/analyzer/pr99716-1.c     | 40 +++++++++++++
 gcc/testsuite/gcc.dg/analyzer/pr99716-2.c     | 34 +++++++++++
 gcc/testsuite/gcc.dg/analyzer/pr99716-3.c     | 16 +++++
 13 files changed, 372 insertions(+), 3 deletions(-)
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/pr93695-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/pr99044-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/pr99044-2.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/pr99716-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/pr99716-2.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/pr99716-3.c
diff mbox series

Patch

diff --git a/gcc/analyzer/engine.cc b/gcc/analyzer/engine.cc
index 5792c142847..d7866b5598b 100644
--- a/gcc/analyzer/engine.cc
+++ b/gcc/analyzer/engine.cc
@@ -1244,6 +1244,31 @@  exploded_node::on_stmt (exploded_graph &eg,
       sm_state_map *new_smap = state->m_checker_states[sm_idx];
       impl_sm_context sm_ctxt (eg, sm_idx, sm, this, &old_state, state,
 			       old_smap, new_smap);
+
+      /* If we're at the def-stmt of an SSA name, then potentially purge
+	 any sm-state for svalues that involve that SSA name.  This avoids
+	 false positives in loops, since a symbolic value referring to the
+	 SSA name will be referring to the previous value of that SSA name.
+	 For example, in:
+	   while ((e = hashmap_iter_next(&iter))) {
+	     struct oid2strbuf *e_strbuf = (struct oid2strbuf *)e;
+	     free (e_strbuf->value);
+	   }
+	 at the def-stmt of e_8:
+	   e_8 = hashmap_iter_next (&iter);
+	 we should purge the "freed" state of:
+	   INIT_VAL(CAST_REG(‘struct oid2strbuf’, (*INIT_VAL(e_8))).value)
+	 which is the "e_strbuf->value" value from the previous iteration,
+	 or we will erroneously report a double-free - the "e_8" within it
+	 refers to the previous value.  */
+      if (tree lhs = gimple_get_lhs (stmt))
+	if (TREE_CODE (lhs) == SSA_NAME)
+	  {
+	    const svalue *sval
+	      = old_state.m_region_model->get_rvalue (lhs, &ctxt);
+	    new_smap->purge_state_involving (sval, eg.get_ext_state ());
+	  }
+
       /* Allow the state_machine to handle the stmt.  */
       if (sm.on_stmt (&sm_ctxt, snode, stmt))
 	unknown_side_effects = false;
diff --git a/gcc/analyzer/program-state.cc b/gcc/analyzer/program-state.cc
index e427fff59d6..fcea5ce436d 100644
--- a/gcc/analyzer/program-state.cc
+++ b/gcc/analyzer/program-state.cc
@@ -586,6 +586,36 @@  sm_state_map::on_unknown_change (const svalue *sval,
     impl_set_state (*iter, (state_machine::state_t)0, NULL, ext_state);
 }
 
+/* Purge state for things involving SVAL.
+   For use when SVAL changes meaning, at the def_stmt on an SSA_NAME.   */
+
+void
+sm_state_map::purge_state_involving (const svalue *sval,
+				     const extrinsic_state &ext_state)
+{
+  /* Currently svalue::involves_p requires this.  */
+  if (sval->get_kind () != SK_INITIAL)
+    return;
+
+  svalue_set svals_to_unset;
+
+  for (map_t::iterator iter = m_map.begin ();
+       iter != m_map.end ();
+       ++iter)
+    {
+      const svalue *key = (*iter).first;
+      entry_t e = (*iter).second;
+      if (!m_sm.can_purge_p (e.m_state))
+	continue;
+      if (key->involves_p (sval))
+	svals_to_unset.add (key);
+    }
+
+  for (svalue_set::iterator iter = svals_to_unset.begin ();
+       iter != svals_to_unset.end (); ++iter)
+    impl_set_state (*iter, (state_machine::state_t)0, NULL, ext_state);
+}
+
 /* Comparator for imposing an order on sm_state_map instances.  */
 
 int
diff --git a/gcc/analyzer/program-state.h b/gcc/analyzer/program-state.h
index d72945d7449..54fdb5b167c 100644
--- a/gcc/analyzer/program-state.h
+++ b/gcc/analyzer/program-state.h
@@ -157,6 +157,9 @@  public:
 			  bool is_mutable,
 			  const extrinsic_state &ext_state);
 
+  void purge_state_involving (const svalue *sval,
+			      const extrinsic_state &ext_state);
+
   iterator_t begin () const { return m_map.begin (); }
   iterator_t end () const { return m_map.end (); }
   size_t elements () const { return m_map.elements (); }
diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc
index 96ed549adf6..fb5dc39fc66 100644
--- a/gcc/analyzer/region-model.cc
+++ b/gcc/analyzer/region-model.cc
@@ -5114,6 +5114,37 @@  test_alloca ()
   ASSERT_EQ (model.get_rvalue (p, &ctxt)->get_kind (), SK_POISONED);
 }
 
+/* Verify that svalue::involves_p works.  */
+
+static void
+test_involves_p ()
+{
+  region_model_manager mgr;
+  tree int_star = build_pointer_type (integer_type_node);
+  tree p = build_global_decl ("p", int_star);
+  tree q = build_global_decl ("q", int_star);
+
+  test_region_model_context ctxt;
+  region_model model (&mgr);
+  const svalue *p_init = model.get_rvalue (p, &ctxt);
+  const svalue *q_init = model.get_rvalue (q, &ctxt);
+
+  ASSERT_TRUE (p_init->involves_p (p_init));
+  ASSERT_FALSE (p_init->involves_p (q_init));
+
+  const region *star_p_reg = mgr.get_symbolic_region (p_init);
+  const region *star_q_reg = mgr.get_symbolic_region (q_init);
+
+  const svalue *init_star_p = mgr.get_or_create_initial_value (star_p_reg);
+  const svalue *init_star_q = mgr.get_or_create_initial_value (star_q_reg);
+
+  ASSERT_TRUE (init_star_p->involves_p (p_init));
+  ASSERT_FALSE (p_init->involves_p (init_star_p));
+  ASSERT_FALSE (init_star_p->involves_p (q_init));
+  ASSERT_TRUE (init_star_q->involves_p (q_init));
+  ASSERT_FALSE (init_star_q->involves_p (p_init));
+}
+
 /* Run all of the selftests within this file.  */
 
 void
@@ -5150,6 +5181,7 @@  analyzer_region_model_cc_tests ()
   test_POINTER_PLUS_EXPR_then_MEM_REF ();
   test_malloc ();
   test_alloca ();
+  test_involves_p ();
 }
 
 } // namespace selftest
diff --git a/gcc/analyzer/svalue.cc b/gcc/analyzer/svalue.cc
index d6305a37b9a..897e84e8464 100644
--- a/gcc/analyzer/svalue.cc
+++ b/gcc/analyzer/svalue.cc
@@ -481,6 +481,40 @@  svalue::cmp_ptr_ptr (const void *p1, const void *p2)
   return cmp_ptr (sval1, sval2);
 }
 
+/* Subclass of visitor for use in implementing svalue::involves_p.  */
+
+class involvement_visitor : public visitor
+{
+public:
+  involvement_visitor (const svalue *needle)
+  : m_needle (needle), m_found (false) {}
+
+  void visit_initial_svalue (const initial_svalue *candidate)
+  {
+    if (candidate == m_needle)
+      m_found = true;
+  }
+
+  bool found_p () const { return m_found; }
+
+private:
+  const svalue *m_needle;
+  bool m_found;
+};
+
+/* Return true iff this svalue is defined in terms of OTHER.  */
+
+bool
+svalue::involves_p (const svalue *other) const
+{
+  /* Currently only implemented for initial_svalue.  */
+  gcc_assert (other->get_kind () == SK_INITIAL);
+
+  involvement_visitor v (other);
+  accept (&v);
+  return v.found_p ();
+}
+
 /* class region_svalue : public svalue.  */
 
 /* Implementation of svalue::dump_to_pp vfunc for region_svalue.  */
diff --git a/gcc/analyzer/svalue.h b/gcc/analyzer/svalue.h
index 672a89cccff..7fe0ba3a603 100644
--- a/gcc/analyzer/svalue.h
+++ b/gcc/analyzer/svalue.h
@@ -136,6 +136,8 @@  public:
   static int cmp_ptr (const svalue *, const svalue *);
   static int cmp_ptr_ptr (const void *, const void *);
 
+  bool involves_p (const svalue *other) const;
+
  protected:
   svalue (complexity c, tree type)
   : m_complexity (c), m_type (type)
diff --git a/gcc/testsuite/gcc.dg/analyzer/attr-malloc-CVE-2019-19078-usb-leak.c b/gcc/testsuite/gcc.dg/analyzer/attr-malloc-CVE-2019-19078-usb-leak.c
index 905d50ec3f9..e086843c42b 100644
--- a/gcc/testsuite/gcc.dg/analyzer/attr-malloc-CVE-2019-19078-usb-leak.c
+++ b/gcc/testsuite/gcc.dg/analyzer/attr-malloc-CVE-2019-19078-usb-leak.c
@@ -192,9 +192,7 @@  static int ath10k_usb_hif_tx_sg(struct ath10k *ar, u8 pipe_id,
 			goto err_free_urb_to_pipe;
 		}
 
-		/* TODO: the loop confuses the double-free checker (another
-		   instance of PR analyzer/93695).  */
-		usb_free_urb(urb); /* { dg-bogus "double-'usb_free_urb' of 'urb'" "" { xfail *-*-* } } */
+		usb_free_urb(urb); /* { dg-bogus "double-'usb_free_urb' of 'urb'" } */
 	}
 
 	return 0;
diff --git a/gcc/testsuite/gcc.dg/analyzer/pr93695-1.c b/gcc/testsuite/gcc.dg/analyzer/pr93695-1.c
new file mode 100644
index 00000000000..e0500c49be7
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/pr93695-1.c
@@ -0,0 +1,53 @@ 
+/* { dg-additional-options "-Wno-analyzer-too-complex" } */
+/* TODO: remove the need for this option (PR analyzer/93695).  */
+
+#define NELEMS 10
+#define ARRAY_SIZE(a) (sizeof (a) / sizeof (a[0]))
+
+void
+test_1 (void)
+{
+  int *p[NELEMS];
+  int i;
+
+  for (i = 0; i < ARRAY_SIZE (p); ++i)
+    p[i] = __builtin_malloc (sizeof (i));
+
+  for (i = 0; i < ARRAY_SIZE (p); ++i)
+    __builtin_free (p [i]);
+}
+
+void
+test_2 (int n)
+{
+  int **p;
+  int i;
+
+  p = (int **)__builtin_malloc (sizeof (int *) * n);
+  if (!p)
+    return;
+
+  for (i = 0; i < n; ++i)
+    p[i] = __builtin_malloc (sizeof (i));
+
+  for (i = 0; i < n; ++i)
+    __builtin_free (p [i]);
+
+  __builtin_free (p);
+}
+
+void
+test_3 (int **p, int n)
+{
+  int i;
+  for (i = 0; i < n; ++i)
+    p[i] = __builtin_malloc (sizeof (i));
+}
+
+void
+test_4 (void **p, int n)
+{
+  int i;
+  for (i = 0; i < n; ++i)
+    __builtin_free (p[i]);
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/pr99044-1.c b/gcc/testsuite/gcc.dg/analyzer/pr99044-1.c
new file mode 100644
index 00000000000..6b5d9010fd9
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/pr99044-1.c
@@ -0,0 +1,60 @@ 
+#include <stdlib.h>
+
+struct hashmap_entry {
+	struct hashmap_entry *next;
+	unsigned int hash;
+};
+
+struct strbuf {
+	size_t alloc;
+	size_t len;
+	char *buf;
+};
+
+struct oid2strbuf {
+	struct hashmap_entry ent; /* must be the first member! */
+	unsigned char key[21];
+	struct strbuf *value;
+};
+
+
+struct hashmap_iter {
+	struct hashmap *map;
+	struct hashmap_entry *next;
+	unsigned int tablepos;
+};
+
+struct hashmap {
+	struct hashmap_entry **table;
+	// hashmap_cmp_fn cmpfn;
+	unsigned int size, tablesize, grow_at, shrink_at;
+	unsigned disallow_rehash : 1;
+};
+void strbuf_init(struct strbuf *, size_t);
+void *hashmap_iter_next(struct hashmap_iter *iter);
+void hashmap_free(struct hashmap *map, int free_entries);
+void hashmap_iter_init(struct hashmap *map, struct hashmap_iter *iter);
+
+void strbuf_release(struct strbuf *sb)
+{
+	if (sb->alloc) {  /* { dg-bogus "use after 'free'" } */
+		free(sb->buf);
+		strbuf_init(sb, 0);
+	}
+}
+
+void oid2strbuf_free(struct hashmap *map) {
+	struct hashmap_iter iter;
+	struct hashmap_entry *e;
+
+	hashmap_iter_init(map, &iter);
+	while ((e = hashmap_iter_next(&iter))) {
+		struct oid2strbuf *e_strbuf = (struct oid2strbuf *)e;
+		strbuf_release(e_strbuf->value); /* { dg-bogus "use after 'free'" } */
+		free(e_strbuf->value);
+		free(e);
+	}
+
+	hashmap_free(map, 0);
+}
+
diff --git a/gcc/testsuite/gcc.dg/analyzer/pr99044-2.c b/gcc/testsuite/gcc.dg/analyzer/pr99044-2.c
new file mode 100644
index 00000000000..fd71d35d7e4
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/pr99044-2.c
@@ -0,0 +1,42 @@ 
+struct node
+{
+  struct node *next;
+};
+
+void test_1 (struct node *n)
+{
+  while (n)
+    {
+      struct node *next = n->next;
+      __builtin_free (n);
+      n = next;
+    }
+}
+
+extern void *get_ptr (void);
+
+void test_2 (void)
+{
+  void *p;
+  while (p = get_ptr ())
+    __builtin_free (p); /* { dg-bogus "double-'free' of 'p'" } */
+}
+
+extern void **get_ptr_ptr (void);
+
+void test_3 (void)
+{
+  void **p;
+  while (p = get_ptr_ptr ())
+    __builtin_free (*p); /* { dg-bogus "double-'free'" } */
+}
+
+void test_4 (void)
+{
+  void *p = (void *)0;
+  while (1)
+    {
+      __builtin_free (p); /* { dg-bogus "double-'free' of 'p'" } */
+      p = get_ptr ();
+    }
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/pr99716-1.c b/gcc/testsuite/gcc.dg/analyzer/pr99716-1.c
new file mode 100644
index 00000000000..6720c3c198b
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/pr99716-1.c
@@ -0,0 +1,40 @@ 
+#include <stdio.h>
+#include <stdlib.h>
+
+void
+test_1 (void)
+{
+  int i;
+
+  for (i = 0; i < 2; ++i) {
+    FILE *fp = fopen ("/tmp/test", "w");
+    fprintf (fp, "hello:%s ", "world");
+    fclose (fp); /* { dg-bogus "double 'fclose'" } */
+  }
+}
+
+void
+test_2 (void)
+{
+  int i;
+
+  for (i = 0; i < 2; ++i) {
+    FILE *fp = fopen ("/tmp/test", "w");
+    fprintf (fp, "hello");
+  }
+} /* { dg-warning "leak of FILE 'fp'" } */
+
+FILE *fp3;
+
+void
+test_3 (FILE **fpp)
+{
+  int i;
+
+  for (i = 0; i < 2; ++i) {
+    *fpp = fopen ("/tmp/test", "w");
+    fprintf (*fpp, "hello");
+    fclose (*fpp); /* { dg-bogus "double 'fclose'" } */
+    *fpp = NULL;
+  }
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/pr99716-2.c b/gcc/testsuite/gcc.dg/analyzer/pr99716-2.c
new file mode 100644
index 00000000000..7c9881c61ff
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/pr99716-2.c
@@ -0,0 +1,34 @@ 
+/* Reduced from
+   https://github.com/libguestfs/libguestfs/blob/e0a11061035d47b118c95706240bcc17fd576edc/tests/mount-local/test-parallel-mount-local.c#L299-L335
+   which is GPLv2 or later.  */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+extern int foo (void);
+
+void
+test_mountpoint (const char *mp)
+{
+  const int nr_passes = 5 + (random () & 31);
+  int pass;
+  int ret = 1;
+  FILE *fp;
+
+  for (pass = 0; pass < nr_passes; ++pass) {
+    if (foo ()) {
+      goto error;
+    }
+    fp = fopen ("file", "w");
+    if (fp == NULL) {
+      goto error;
+    }
+    fprintf (fp, "hello world\n");
+    fclose (fp); /* { dg-bogus "double 'fclose'" } */
+  }
+
+  ret = 0;
+
+ error:
+  exit (ret);
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/pr99716-3.c b/gcc/testsuite/gcc.dg/analyzer/pr99716-3.c
new file mode 100644
index 00000000000..77d450ea3be
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/pr99716-3.c
@@ -0,0 +1,16 @@ 
+#include <stdlib.h>
+
+extern void foo (void *);
+
+void
+test_1 (int nr_passes)
+{
+  int pass;
+  void *p;
+
+  for (pass = 0; pass < nr_passes; ++pass) {
+    p = malloc (1024);
+    foo (p);
+    free (p);
+  }
+}