]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
Add sanopt support for UBSAN_PTR.
authorMartin Liska <mliska@suse.cz>
Fri, 6 Oct 2017 14:14:14 +0000 (16:14 +0200)
committerMartin Liska <marxin@gcc.gnu.org>
Fri, 6 Oct 2017 14:14:14 +0000 (14:14 +0000)
2017-10-06  Martin Liska  <mliska@suse.cz>

* sanopt.c (struct sanopt_tree_triplet_hash): Remove inline
keyword for member functions.
(struct sanopt_tree_couple): New struct.
(struct sanopt_tree_couple_hash): New function.
(struct sanopt_ctx): Add new hash_map.
(has_dominating_ubsan_ptr_check): New function.
(record_ubsan_ptr_check_stmt): Likewise.
(maybe_optimize_ubsan_ptr_ifn): Likewise.
(sanopt_optimize_walker): Handle IFN_UBSAN_PTR.
(pass_sanopt::execute): Handle also SANITIZE_POINTER_OVERFLOW.
2017-10-06  Martin Liska  <mliska@suse.cz>

* c-c++-common/ubsan/ptr-overflow-sanitization-1.c: New test.

From-SVN: r253492

gcc/ChangeLog
gcc/sanopt.c
gcc/testsuite/ChangeLog
gcc/testsuite/c-c++-common/ubsan/ptr-overflow-sanitization-1.c [new file with mode: 0644]

index ecc6e5ba0c9760ce8bb9eb427f7ad5092bc016a0..b56f7e637e08f13bba4e692f274aca2a48bdad3a 100644 (file)
@@ -1,3 +1,16 @@
+2017-10-06  Martin Liska  <mliska@suse.cz>
+
+       * sanopt.c (struct sanopt_tree_triplet_hash): Remove inline
+       keyword for member functions.
+       (struct sanopt_tree_couple): New struct.
+       (struct sanopt_tree_couple_hash): New function.
+       (struct sanopt_ctx): Add new hash_map.
+       (has_dominating_ubsan_ptr_check): New function.
+       (record_ubsan_ptr_check_stmt): Likewise.
+       (maybe_optimize_ubsan_ptr_ifn): Likewise.
+       (sanopt_optimize_walker): Handle IFN_UBSAN_PTR.
+       (pass_sanopt::execute): Handle also SANITIZE_POINTER_OVERFLOW.
+
 2017-10-06  Sudakshina Das  <sudi.das@arm.com>
 
        PR target/82440
index d17c7db3321b37acf1fe3be813425ae3a7850e79..997bcfd3df71fc058524e5456caac1314066afea 100644 (file)
@@ -45,6 +45,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "cfghooks.h"
 #include "tree-dfa.h"
 #include "tree-ssa.h"
+#include "varasm.h"
 
 /* This is used to carry information about basic blocks.  It is
    attached to the AUX field of the standard CFG block.  */
@@ -105,7 +106,7 @@ struct sanopt_tree_triplet_hash : typed_noop_remove <sanopt_tree_triplet>
   typedef sanopt_tree_triplet value_type;
   typedef sanopt_tree_triplet compare_type;
 
-  static inline hashval_t
+  static hashval_t
   hash (const sanopt_tree_triplet &ref)
   {
     inchash::hash hstate (0);
@@ -115,7 +116,7 @@ struct sanopt_tree_triplet_hash : typed_noop_remove <sanopt_tree_triplet>
     return hstate.end ();
   }
 
-  static inline bool
+  static bool
   equal (const sanopt_tree_triplet &ref1, const sanopt_tree_triplet &ref2)
   {
     return operand_equal_p (ref1.t1, ref2.t1, 0)
@@ -123,31 +124,86 @@ struct sanopt_tree_triplet_hash : typed_noop_remove <sanopt_tree_triplet>
           && operand_equal_p (ref1.t3, ref2.t3, 0);
   }
 
-  static inline void
+  static void
   mark_deleted (sanopt_tree_triplet &ref)
   {
     ref.t1 = reinterpret_cast<tree> (1);
   }
 
-  static inline void
+  static void
   mark_empty (sanopt_tree_triplet &ref)
   {
     ref.t1 = NULL;
   }
 
-  static inline bool
+  static bool
   is_deleted (const sanopt_tree_triplet &ref)
   {
-    return ref.t1 == (void *) 1;
+    return ref.t1 == reinterpret_cast<tree> (1);
   }
 
-  static inline bool
+  static bool
   is_empty (const sanopt_tree_triplet &ref)
   {
     return ref.t1 == NULL;
   }
 };
 
+/* Tree couple for ptr_check_map.  */
+struct sanopt_tree_couple
+{
+  tree ptr;
+  bool pos_p;
+};
+
+/* Traits class for tree triplet hash maps below.  */
+
+struct sanopt_tree_couple_hash : typed_noop_remove <sanopt_tree_couple>
+{
+  typedef sanopt_tree_couple value_type;
+  typedef sanopt_tree_couple compare_type;
+
+  static hashval_t
+  hash (const sanopt_tree_couple &ref)
+  {
+    inchash::hash hstate (0);
+    inchash::add_expr (ref.ptr, hstate);
+    hstate.add_int (ref.pos_p);
+    return hstate.end ();
+  }
+
+  static bool
+  equal (const sanopt_tree_couple &ref1, const sanopt_tree_couple &ref2)
+  {
+    return operand_equal_p (ref1.ptr, ref2.ptr, 0)
+          && ref1.pos_p == ref2.pos_p;
+  }
+
+  static void
+  mark_deleted (sanopt_tree_couple &ref)
+  {
+    ref.ptr = reinterpret_cast<tree> (1);
+  }
+
+  static void
+  mark_empty (sanopt_tree_couple &ref)
+  {
+    ref.ptr = NULL;
+  }
+
+  static bool
+  is_deleted (const sanopt_tree_couple &ref)
+  {
+    return ref.ptr == reinterpret_cast<tree> (1);
+  }
+
+  static bool
+  is_empty (const sanopt_tree_couple &ref)
+  {
+    return ref.ptr == NULL;
+  }
+};
+
 /* This is used to carry various hash maps and variables used
    in sanopt_optimize_walker.  */
 
@@ -166,6 +222,10 @@ struct sanopt_ctx
      that virtual table pointer.  */
   hash_map<sanopt_tree_triplet_hash, auto_vec<gimple *> > vptr_check_map;
 
+  /* This map maps a couple (tree and boolean) to a vector of UBSAN_PTR
+     call statements that check that pointer overflow.  */
+  hash_map<sanopt_tree_couple_hash, auto_vec<gimple *> > ptr_check_map;
+
   /* Number of IFN_ASAN_CHECK statements.  */
   int asan_num_accesses;
 
@@ -344,6 +404,179 @@ maybe_optimize_ubsan_null_ifn (struct sanopt_ctx *ctx, gimple *stmt)
   return remove;
 }
 
+/* Return true when pointer PTR for a given CUR_OFFSET is already sanitized
+   in a given sanitization context CTX.  */
+
+static bool
+has_dominating_ubsan_ptr_check (sanopt_ctx *ctx, tree ptr,
+                               offset_int &cur_offset)
+{
+  bool pos_p = !wi::neg_p (cur_offset);
+  sanopt_tree_couple couple;
+  couple.ptr = ptr;
+  couple.pos_p = pos_p;
+
+  auto_vec<gimple *> &v = ctx->ptr_check_map.get_or_insert (couple);
+  gimple *g = maybe_get_dominating_check (v);
+  if (!g)
+    return false;
+
+  /* We already have recorded a UBSAN_PTR check for this pointer.  Perhaps we
+     can drop this one.  But only if this check doesn't specify larger offset.
+     */
+  tree offset = gimple_call_arg (g, 1);
+  gcc_assert (TREE_CODE (offset) == INTEGER_CST);
+  offset_int ooffset = wi::sext (wi::to_offset (offset), POINTER_SIZE);
+
+  if (pos_p)
+    {
+      if (wi::les_p (cur_offset, ooffset))
+       return true;
+    }
+  else if (!pos_p && wi::les_p (ooffset, cur_offset))
+    return true;
+
+  return false;
+}
+
+/* Record UBSAN_PTR check of given context CTX.  Register pointer PTR on
+   a given OFFSET that it's handled by GIMPLE STMT.  */
+
+static void
+record_ubsan_ptr_check_stmt (sanopt_ctx *ctx, gimple *stmt, tree ptr,
+                            const offset_int &offset)
+{
+  sanopt_tree_couple couple;
+  couple.ptr = ptr;
+  couple.pos_p = !wi::neg_p (offset);
+
+  auto_vec<gimple *> &v = ctx->ptr_check_map.get_or_insert (couple);
+  v.safe_push (stmt);
+}
+
+/* Optimize away redundant UBSAN_PTR calls.  */
+
+static bool
+maybe_optimize_ubsan_ptr_ifn (sanopt_ctx *ctx, gimple *stmt)
+{
+  HOST_WIDE_INT bitsize, bitpos;
+  machine_mode mode;
+  int volatilep = 0, reversep, unsignedp = 0;
+  tree offset;
+
+  gcc_assert (gimple_call_num_args (stmt) == 2);
+  tree ptr = gimple_call_arg (stmt, 0);
+  tree off = gimple_call_arg (stmt, 1);
+
+  if (TREE_CODE (off) != INTEGER_CST)
+    return false;
+
+  if (integer_zerop (off))
+    return true;
+
+  offset_int cur_offset = wi::sext (wi::to_offset (off), POINTER_SIZE);
+  if (has_dominating_ubsan_ptr_check (ctx, ptr, cur_offset))
+    return true;
+
+  tree base = ptr;
+  if (TREE_CODE (base) == ADDR_EXPR)
+    {
+      base = TREE_OPERAND (base, 0);
+
+      base = get_inner_reference (base, &bitsize, &bitpos, &offset, &mode,
+                                 &unsignedp, &reversep, &volatilep);
+      if (offset == NULL_TREE && DECL_P (base))
+       {
+         gcc_assert (!DECL_REGISTER (base));
+         offset_int expr_offset = bitpos / BITS_PER_UNIT;
+         offset_int total_offset = expr_offset + cur_offset;
+         if (total_offset != wi::sext (total_offset, POINTER_SIZE))
+           {
+             record_ubsan_ptr_check_stmt (ctx, stmt, ptr, cur_offset);
+             return false;
+           }
+
+         /* If BASE is a fixed size automatic variable or
+            global variable defined in the current TU, we don't have
+            to instrument anything if offset is within address
+            of the variable.  */
+         if ((VAR_P (base)
+              || TREE_CODE (base) == PARM_DECL
+              || TREE_CODE (base) == RESULT_DECL)
+             && DECL_SIZE_UNIT (base)
+             && TREE_CODE (DECL_SIZE_UNIT (base)) == INTEGER_CST
+             && (!is_global_var (base) || decl_binds_to_current_def_p (base)))
+           {
+             offset_int base_size = wi::to_offset (DECL_SIZE_UNIT (base));
+             if (bitpos >= 0
+                 && wi::les_p (total_offset, base_size))
+               {
+                 if (!wi::neg_p (total_offset)
+                     && wi::les_p (total_offset, base_size))
+                   return true;
+               }
+           }
+
+         /* Following expression: UBSAN_PTR (&MEM_REF[ptr + x], y) can be
+            handled as follows:
+
+            1) sign (x) == sign (y), then check for dominating check of (x + y)
+            2) sign (x) != sign (y), then first check if we have a dominating
+               check for ptr + x.  If so, then we have 2 situations:
+               a) sign (x) == sign (x + y), here we are done, example:
+                  UBSAN_PTR (&MEM_REF[ptr + 100], -50)
+               b) check for dominating check of ptr + x + y.
+            */
+
+         bool sign_cur_offset = !wi::neg_p (cur_offset);
+         bool sign_expr_offset = bitpos >= 0;
+
+         tree base_addr
+           = build1 (ADDR_EXPR, build_pointer_type (TREE_TYPE (base)), base);
+
+         bool add = false;
+         if (sign_cur_offset == sign_expr_offset)
+           {
+             if (has_dominating_ubsan_ptr_check (ctx, base_addr, total_offset))
+               return true;
+             else
+               add = true;
+           }
+         else
+           {
+             if (!has_dominating_ubsan_ptr_check (ctx, base_addr, expr_offset))
+               ; /* Don't record base_addr + expr_offset, it's not a guarding
+                    check.  */
+             else
+               {
+                 bool sign_total_offset = !wi::neg_p (total_offset);
+                 if (sign_expr_offset == sign_total_offset)
+                   return true;
+                 else
+                   {
+                     if (has_dominating_ubsan_ptr_check (ctx, base_addr,
+                                                         total_offset))
+                       return true;
+                     else
+                       add = true;
+                   }
+               }
+           }
+
+         /* Record a new dominating check for base_addr + total_offset.  */
+         if (add && !operand_equal_p (base, base_addr, 0))
+           record_ubsan_ptr_check_stmt (ctx, stmt, base_addr,
+                                        total_offset);
+       }
+    }
+
+  /* For this PTR we don't have any UBSAN_PTR stmts recorded, so there's
+     nothing to optimize yet.  */
+  record_ubsan_ptr_check_stmt (ctx, stmt, ptr, cur_offset);
+
+  return false;
+}
+
 /* Optimize away redundant UBSAN_VPTR calls.  The second argument
    is the value loaded from the virtual table, so rely on FRE to find out
    when we can actually optimize.  */
@@ -586,6 +819,9 @@ sanopt_optimize_walker (basic_block bb, struct sanopt_ctx *ctx)
          case IFN_UBSAN_VPTR:
            remove = maybe_optimize_ubsan_vptr_ifn (ctx, stmt);
            break;
+         case IFN_UBSAN_PTR:
+           remove = maybe_optimize_ubsan_ptr_ifn (ctx, stmt);
+           break;
          case IFN_ASAN_CHECK:
            if (asan_check_optimize)
              remove = maybe_optimize_asan_check_ifn (ctx, stmt);
@@ -604,15 +840,22 @@ sanopt_optimize_walker (basic_block bb, struct sanopt_ctx *ctx)
          /* Drop this check.  */
          if (dump_file && (dump_flags & TDF_DETAILS))
            {
-             fprintf (dump_file, "Optimizing out\n  ");
+             fprintf (dump_file, "Optimizing out: ");
              print_gimple_stmt (dump_file, stmt, 0, dump_flags);
-             fprintf (dump_file, "\n");
            }
          unlink_stmt_vdef (stmt);
          gsi_remove (&gsi, true);
        }
       else
-       gsi_next (&gsi);
+       {
+         if (dump_file && (dump_flags & TDF_DETAILS))
+           {
+             fprintf (dump_file, "Leaving: ");
+             print_gimple_stmt (dump_file, stmt, 0, dump_flags);
+           }
+
+         gsi_next (&gsi);
+       }
     }
 
   if (asan_check_optimize)
@@ -1008,7 +1251,7 @@ pass_sanopt::execute (function *fun)
   if (optimize
       && (flag_sanitize
          & (SANITIZE_NULL | SANITIZE_ALIGNMENT
-            | SANITIZE_ADDRESS | SANITIZE_VPTR)))
+            | SANITIZE_ADDRESS | SANITIZE_VPTR | SANITIZE_POINTER_OVERFLOW)))
     asan_num_accesses = sanopt_optimize (fun, &contains_asan_mark);
   else if (flag_sanitize & SANITIZE_ADDRESS)
     {
@@ -1103,9 +1346,8 @@ pass_sanopt::execute (function *fun)
 
          if (dump_file && (dump_flags & TDF_DETAILS))
            {
-             fprintf (dump_file, "Expanded\n  ");
+             fprintf (dump_file, "Expanded: ");
              print_gimple_stmt (dump_file, stmt, 0, dump_flags);
-             fprintf (dump_file, "\n");
            }
 
          if (!no_next)
index 2e6ca4f4bb62efbae565e8ec7d1f65286530dcf4..3172683aa4a7e2700464af2d2a773f94ee63c80c 100644 (file)
@@ -1,3 +1,7 @@
+2017-10-06  Martin Liska  <mliska@suse.cz>
+
+       * c-c++-common/ubsan/ptr-overflow-sanitization-1.c: New test.
+
 2017-10-06  Sudakshina Das  <sudi.das@arm.com>
 
        * gcc.target/aarch64/bic_imm_1.c: New test.
diff --git a/gcc/testsuite/c-c++-common/ubsan/ptr-overflow-sanitization-1.c b/gcc/testsuite/c-c++-common/ubsan/ptr-overflow-sanitization-1.c
new file mode 100644 (file)
index 0000000..42c1452
--- /dev/null
@@ -0,0 +1,80 @@
+/* { dg-require-effective-target lp64 } */
+/* { dg-options "-O -fsanitize=pointer-overflow" } */
+/* { dg-skip-if "" { *-*-* } "-flto" } */
+
+#define SMAX   __PTRDIFF_MAX__
+
+void foo(void)
+{
+  char *p;
+  char *p2;
+  char b[1];
+  char c[1];
+
+  p = b + SMAX; /* pointer overflow check is needed */
+  p = b;
+  p++;
+  p2 = p + 1000;
+  p2 = p + 999;
+
+  p = b + SMAX;
+  p2 = p + 1; /* pointer overflow check is needed */
+
+  p = b;
+  p--; /* pointer overflow check is needed */
+  p2 = p + 1;
+  p2 = p + 2;
+
+  p = b - SMAX; /* pointer overflow check is needed */
+  p2 = p + (SMAX - 2); /* b - 2: pointer overflow check is needed */
+  p2 = p + (SMAX - 1); /* b - 1: pointer overflow check is needed */
+  p2 = p + SMAX; /* b: pointer overflow check is needed */
+  p2++; /* b + 1 */
+
+  p = c;
+  p++; /* c + 1 */
+  p = c - SMAX; /* pointer overflow check is needed */
+  p2 = p + SMAX; /* c: pointer overflow check is needed */
+  p2++; /* c + 1 */
+}
+
+void bar(char *ptr)
+{
+  char *p = ptr - 1000; /* pointer overflow check is needed */
+  p = ptr + 1000; /* pointer overflow check is needed */
+  p -= 2000; /* pointer overflow check is needed */
+}
+
+void baz(char *ptr)
+{
+  char **p = &ptr;
+  char **p2 = p + 20; /* pointer overflow check is needed */
+  p2--;
+}
+
+void positive_and_positive (char *ptr)
+{
+  char **p = &ptr;
+  char **p2 = p + 100; /* pointer overflow check is needed */
+  p2 = p + 10;
+  p += 50; 
+}
+
+void negative_to_positive (char *ptr)
+{
+  char **p = &ptr;
+  char **p2 = p + 20; /* pointer overflow check is needed */
+  p2 = p - 10; /* pointer overflow check is needed */
+  p2 += 15;
+}
+
+void negative_to_negative (char *ptr)
+{
+  char **p = &ptr;
+  char **p2 = p - 20; /* pointer overflow check is needed */
+  p2 = p - 20;
+  p2 += 5;
+}
+
+
+/* { dg-final { scan-assembler-times "call\\s+__ubsan_handle_pointer_overflow" 17 } } */