On 05/12/2016 12:41 PM, Jakub Jelinek wrote:
> On Wed, May 11, 2016 at 02:54:01PM +0200, Martin Liška wrote:
>> On 05/06/2016 02:22 PM, Jakub Jelinek wrote:
>>> On Fri, May 06, 2016 at 01:04:30PM +0200, Martin Liška wrote:
>>>> I've started working on the patch couple of month go, basically after
>>>> a brief discussion with Jakub on IRC.
>>>>
>>>> I'm sending the initial version which can successfully run instrumented
>>>> tramp3d, postgresql server and Inkscape. It catches the basic set of
>>>> examples which are added in following patch.
>>>>
>>>> The implementation is quite straightforward as works in following steps:
>>>>
>>>> 1) Every local variable stack slot is poisoned at the very beginning of a 
>>>> function (RTL emission)
>>>> 2) In gimplifier, once we spot a DECL_EXPR, a variable is unpoisoned (by 
>>>> emitting ASAN_MARK builtin)
>>>> and the variable is marked as addressable
>>>
>>> Not all vars have DECL_EXPRs though.
> 
> Just random comments from quick skim, need to find enough spare time to
> actually try it and see how it works.
> 
>> Yeah, I've spotted one interesting example which is part of LLVM's testsuite:
>>
>> struct IntHolder {
>>   int val;
>> };
>>
>> const IntHolder *saved;
>>
>> void save(const IntHolder &holder) {
>>   saved = &holder;
>> }
>>
>> int main(int argc, char *argv[]) {
>>   save({10});
>>   int x = saved->val;  // BOOM
>>   return x;
>> }
>>
>> It would be also good to handle such temporaries. Any suggestions how to 
>> handle that in gimplifier?
> 
> Dunno, guess you need to do something in the FE for it already (talk to
> Jason?).  At least in *.original dump there is already:
>   <<cleanup_point <<< Unknown tree: expr_stmt
>   save ((const struct IntHolder &) &TARGET_EXPR <D.2263, {.val=10}>) >>>>>;
>     int x = (int) saved->val;
>   return <retval> = x;
> and the info on where the D.2263 temporary goes out of scope is lost.

Thanks for sample, I will ask Jason to help me with that.

> 
>> Apart from that, second version of the patch changes:
>> + fixed issues with missing stack unpoisoning; currently, I mark all 
>> VAR_DECLs that
>> are in ASAN_MARK internal fns and stack prologue/epilogue is emitted just 
>> for these vars
>> + removed unneeded hunks (tree-vect-patterns.c and asan_poisoning.cc)
>> + LABEL unpoisoning code makes stable sort for variables that were already 
>> used in the context
>> + stack poisoning hasn't worked for -O1+ due to following guard in asan.c
>>  /* Automatic vars in the current function will be always accessible.  */
>> + direct shadow memory poisoning/unpoisoning code is introduced - in both 
>> scenarios (RTL and GIMPLE),
>> I would appreciate feedback if storing multiple bytes is fine? What is the 
>> maximum memory wide
>> store mode supported by a target? How can I get such information?
>> + the maximum object size handled by a direct emission is guarded by 
>> use-after-scope-direct-emission-threshold
>> parameter; initial value (256B) should maximally emit store of 32B
> 
> Would be better if user visible param was in bytes rather than bits IMHO.
> 

Well, the size of an object is in bytes, but as we map every 8 (yeah, that's 
configurable, I'm quite curious about
real respecting of ASAN_SHADOW_SHIFT) bytes of real memory to
a single byte in shadow memory, thus the division by 8 is needed.

>> Yeah, depends because of:
>>
>> static inline bool
>> asan_sanitize_use_after_scope (void)
>> {
>>   return ((flag_sanitize & SANITIZE_ADDRESS_USE_AFTER_SCOPE)
>>        == SANITIZE_ADDRESS_USE_AFTER_SCOPE
>>        && flag_stack_reuse == SR_NONE
>>        && ASAN_STACK);
>> }
>>
>> Where ASAN_STACK comes from params.h.
> 
> I'd prefer just prototype the function in the header and define in asan.c
> or some other source file.  Or maybe split it, do the important case
> (flag_sanitize check) inline and call out of line function for the rest.
> Why do you check flag_stack_reuse?  I thought you'd arrange for it to be
> different when -fsanitize=use-after-scope?

Right, the sanitization does not relate to flag_stack, thus removing the 
dependency,
we can remove need of including params.h in various places.

> 
>> @@ -243,6 +243,11 @@ static unsigned HOST_WIDE_INT asan_shadow_offset_value;
>>  static bool asan_shadow_offset_computed;
>>  static vec<char *> sanitized_sections;
>>  
>> +/* Set of variable declarations that are going to be guarded by
>> +   use-after-scope sanitizer.  */
>> +
>> +static hash_set <tree> asan_handled_variables(13);
> 
> Not sure about the formatting here, don't we use xxx<arg> instead of xxx <arg>
> ?  And I'd expect space before (.

Yeah, done.

>> @@ -1020,6 +1020,91 @@ asan_function_start (void)
>>                       current_function_funcdef_no);
>>  }
>>  
>> +/* Return number of shadow bytes that are occupied by a local variable
>> +   of SIZE bytes.  */
>> +
>> +static unsigned HOST_WIDE_INT
>> +get_shadow_memory_size (unsigned HOST_WIDE_INT size)
>> +{
>> +  /* Round up size of object.  */
>> +  unsigned HOST_WIDE_INT r;
>> +  if ((r = size % BITS_PER_UNIT) != 0)
>> +    size += BITS_PER_UNIT - r;
> 
> Isn't there a ROUND_UP macro?

Thanks. I was looking for the macro.

I'm sending v3 where all of aforementioned notes were handled.

Thanks,
Martin

> 
>       Jakub
> 

>From 59ac662acb13510305c6db84c4c5273dab7b4fcc Mon Sep 17 00:00:00 2001
From: marxin <mli...@suse.cz>
Date: Tue, 3 May 2016 15:35:22 +0200
Subject: [PATCH 1/3] Introduce -fsanitize=use-after-scope

gcc/ChangeLog:

2016-05-10  Martin Liska  <mli...@suse.cz>

	* asan.c (enum asan_check_flags): Enum is moved to a header
	file.
	(asan_init_shadow_ptr_types): Add new shadow_ptr_type.
	(get_shadow_memory_size): New function.
	(asan_poison_stack_variables): Likewise.
	(asan_emit_stack_protection): Poison and unpoison stack
	variables.
	(build_shadow_mem_access): New arguments is added.
	(instrument_derefs): Do not skip automatic variables if we
	instrument for use-after-scope.
	(asan_store_shadow_bytes): New function.
	(asan_expand_mark_ifn): Likewise.
	* asan.h (enum asan_mark_flags): Move from the source file.
	(asan_sanitize_use_after_scope): New function.
	(asan_no_sanitize_address_p): Likewise.
	* cfgexpand.c (asan_sanitize_stack_p): Use
	asan_no_sanitize_address_p.
	* flag-types.h (enum sanitize_code): Add
	SANITIZE_ADDRESS_USE_AFTER_SCOPE enum value.
	* gimplify.c (asan_poison_variable): New function.
	(gimplify_bind_expr): Unpoison stack variables.
	(gimplify_decl_expr): Poison stack variables.
	(gimplify_expr): Unpoison stack variables that precede a label.
	(gimplify_function_tree): Guard that all local variables
	(sort_by_decl_uid): New function.
	are properly unpoisoned.
	* internal-fn.c (expand_ASAN_MARK): New function.
	* internal-fn.def: Declare ASAN_MARK.
	* opts.c (finish_options): Handle interaction between -fstack-reuse
	and -fsanitize=use-after-scope.
	* params.def: Add new param use-after-scope-direct-emission-threshold.
	* params.h: Define the param.
	* sanitizer.def: Define __asan_poison_stack_memory and
	__asan_unpoison_stack_memory.
	* sanopt.c (pass_sanopt::execute): Expand ASAN_MARK internal
	fns.
	* hash-set.h (hash_set::empty): New function.
---
 gcc/asan.c          | 253 ++++++++++++++++++++++++++++++++++++++++++++++------
 gcc/asan.h          |  46 ++++++++--
 gcc/cfgexpand.c     |  15 ++--
 gcc/flag-types.h    |   5 +-
 gcc/gimplify.c      | 118 +++++++++++++++++++++---
 gcc/hash-set.h      |   7 ++
 gcc/internal-fn.c   |   9 ++
 gcc/internal-fn.def |   1 +
 gcc/opts.c          |  14 +++
 gcc/params.def      |   6 ++
 gcc/params.h        |   2 +
 gcc/sanitizer.def   |   4 +
 gcc/sanopt.c        |   3 +
 13 files changed, 436 insertions(+), 47 deletions(-)

diff --git a/gcc/asan.c b/gcc/asan.c
index 71095fb..38a7c90 100644
--- a/gcc/asan.c
+++ b/gcc/asan.c
@@ -243,6 +243,11 @@ static unsigned HOST_WIDE_INT asan_shadow_offset_value;
 static bool asan_shadow_offset_computed;
 static vec<char *> sanitized_sections;
 
+/* Set of variable declarations that are going to be guarded by
+   use-after-scope sanitizer.  */
+
+static hash_set<tree> asan_handled_variables (13);
+
 /* Sets shadow offset to value in string VAL.  */
 
 bool
@@ -313,22 +318,13 @@ asan_shadow_offset ()
 
 alias_set_type asan_shadow_set = -1;
 
-/* Pointer types to 1 resp. 2 byte integers in shadow memory.  A separate
+/* Pointer types to 1, 2 or 4 byte integers in shadow memory.  A separate
    alias set is used for all shadow memory accesses.  */
-static GTY(()) tree shadow_ptr_types[2];
+static GTY(()) tree shadow_ptr_types[3];
 
 /* Decl for __asan_option_detect_stack_use_after_return.  */
 static GTY(()) tree asan_detect_stack_use_after_return;
 
-/* Various flags for Asan builtins.  */
-enum asan_check_flags
-{
-  ASAN_CHECK_STORE = 1 << 0,
-  ASAN_CHECK_SCALAR_ACCESS = 1 << 1,
-  ASAN_CHECK_NON_ZERO_LEN = 1 << 2,
-  ASAN_CHECK_LAST = 1 << 3
-};
-
 /* Hashtable support for memory references used by gimple
    statements.  */
 
@@ -342,7 +338,7 @@ struct asan_mem_ref
   HOST_WIDE_INT access_size;
 };
 
-object_allocator <asan_mem_ref> asan_mem_ref_pool ("asan_mem_ref");
+object_allocator<asan_mem_ref> asan_mem_ref_pool ("asan_mem_ref");
 
 /* Initializes an instance of asan_mem_ref.  */
 
@@ -931,12 +927,16 @@ static void
 asan_init_shadow_ptr_types (void)
 {
   asan_shadow_set = new_alias_set ();
-  shadow_ptr_types[0] = build_distinct_type_copy (signed_char_type_node);
-  TYPE_ALIAS_SET (shadow_ptr_types[0]) = asan_shadow_set;
-  shadow_ptr_types[0] = build_pointer_type (shadow_ptr_types[0]);
-  shadow_ptr_types[1] = build_distinct_type_copy (short_integer_type_node);
-  TYPE_ALIAS_SET (shadow_ptr_types[1]) = asan_shadow_set;
-  shadow_ptr_types[1] = build_pointer_type (shadow_ptr_types[1]);
+  tree types[3] = { signed_char_type_node, short_integer_type_node,
+		    integer_type_node };
+
+  for (unsigned i = 0; i < 3; i++)
+    {
+      shadow_ptr_types[i] = build_distinct_type_copy (types[i]);
+      TYPE_ALIAS_SET (shadow_ptr_types[i]) = asan_shadow_set;
+      shadow_ptr_types[i] = build_pointer_type (shadow_ptr_types[i]);
+    }
+
   initialize_sanitizer_builtins ();
 }
 
@@ -1020,6 +1020,86 @@ asan_function_start (void)
 			 current_function_funcdef_no);
 }
 
+/* Return number of shadow bytes that are occupied by a local variable
+   of SIZE bytes.  */
+
+static unsigned HOST_WIDE_INT
+get_shadow_memory_size (unsigned HOST_WIDE_INT size)
+{
+  return ROUND_UP (size, BITS_PER_UNIT) / BITS_PER_UNIT;
+}
+
+/* Depending on POISON flag, emit a call to poison (or unpoison) stack memory
+   allocated for local variables, localted in OFFSETS.  LENGTH is number
+   of OFFSETS, BASE is the register holding the stack base,
+   against which OFFSETS array offsets are relative to.  BASE_OFFSET represents
+   an offset requested by alignment and similar stuff.  */
+
+static void
+asan_poison_stack_variables (rtx shadow_base, rtx base,
+			     HOST_WIDE_INT base_offset,
+			     HOST_WIDE_INT *offsets, int length,
+			     tree *decls, bool poison)
+{
+  if (asan_sanitize_use_after_scope ())
+    for (int l = length - 2; l > 0; l -= 2)
+      {
+	tree decl = decls[l / 2 - 1];
+	HOST_WIDE_INT var_offset = offsets[l];
+	HOST_WIDE_INT var_end_offset = offsets[l - 1];
+	if (!asan_handled_variables.contains (decl))
+	  {
+	    if (dump_file && (dump_flags & TDF_DETAILS))
+	      fprintf (dump_file, "Skipping stack emission for variable: %s "
+		       "(%ldB)\n",
+		       IDENTIFIER_POINTER (DECL_NAME (decl)),
+		       var_end_offset - var_offset);
+	    continue;
+	  }
+
+	rtx source = expand_binop (Pmode, add_optab, base,
+				   gen_int_mode
+				    (var_offset - base_offset, Pmode),
+				   NULL_RTX, 1, OPTAB_DIRECT);
+
+	HOST_WIDE_INT size = var_end_offset - var_offset;
+	if (size <= ASAN_PARAM_USE_AFTER_SCOPE_DIRECT_EMISSION_THRESHOLD)
+	  {
+	    unsigned HOST_WIDE_INT shadow_size
+	      = get_shadow_memory_size (size);
+
+	    rtx shadow_mem = gen_rtx_MEM (SImode, shadow_base);
+	    rtx var_mem = adjust_address (shadow_mem, QImode,
+					  (var_offset - base_offset)
+					  >> ASAN_SHADOW_SHIFT);
+
+	    char c = poison ? ASAN_STACK_MAGIC_USE_AFTER_SCOPE : 0;
+	    for (unsigned i = 0; i < shadow_size; ++i)
+	      {
+		emit_move_insn (var_mem, gen_int_mode (c, QImode));
+		var_mem = adjust_address (var_mem, QImode, 1);
+	      }
+	  }
+	else
+	  {
+	    rtx size_rtx = GEN_INT (size);
+	    const char *fname = poison ?  "__asan_poison_stack_memory"
+	      :"__asan_unpoison_stack_memory";
+	    rtx ret = init_one_libfunc (fname);
+	    emit_library_call (ret, LCT_NORMAL, VOIDmode, 2, source, ptr_mode,
+			       size_rtx, TYPE_MODE (pointer_sized_int_node));
+	  }
+
+	if (dump_file && (dump_flags & TDF_DETAILS))
+	  fprintf (dump_file, "Emitting stack %s for variable: %s"
+		   "(%ldB)\n",
+		   poison ? "poisoning" : "unpoisoning",
+		   IDENTIFIER_POINTER (DECL_NAME (decl)),
+		   var_end_offset - var_offset);
+      }
+}
+
+
 /* Insert code to protect stack vars.  The prologue sequence should be emitted
    directly, epilogue sequence returned.  BASE is the register holding the
    stack base, against which OFFSETS array offsets are relative to, OFFSETS
@@ -1228,6 +1308,11 @@ asan_emit_stack_protection (rtx base, rtx pbase, unsigned int alignb,
 	}
       cur_shadow_byte = ASAN_STACK_MAGIC_MIDDLE;
     }
+
+  /* Poison stack variables at the very beginning of a function.  */
+  asan_poison_stack_variables (shadow_base, base, base_offset, offsets, length,
+			       decls, true);
+
   do_pending_stack_adjust ();
 
   /* Construct epilogue sequence.  */
@@ -1309,6 +1394,14 @@ asan_emit_stack_protection (rtx base, rtx pbase, unsigned int alignb,
       asan_clear_shadow (shadow_mem, last_size >> ASAN_SHADOW_SHIFT);
     }
 
+  /* Unpoison stack variables at the end of a function.  As the former
+     stack memory can be reused, we have to unpoison the memory.  */
+  asan_poison_stack_variables (shadow_base, base, base_offset, offsets, length,
+			       decls, false);
+
+  /* Clean-up set with instrumented stack variables.  */
+  asan_handled_variables.empty ();
+
   do_pending_stack_adjust ();
   if (lab)
     emit_label (lab);
@@ -1593,7 +1686,8 @@ insert_if_then_before_iter (gcond *cond,
 
 static tree
 build_shadow_mem_access (gimple_stmt_iterator *gsi, location_t location,
-			 tree base_addr, tree shadow_ptr_type)
+			 tree base_addr, tree shadow_ptr_type,
+			 bool return_ptr = false)
 {
   tree t, uintptr_type = TREE_TYPE (base_addr);
   tree shadow_type = TREE_TYPE (shadow_ptr_type);
@@ -1616,11 +1710,15 @@ build_shadow_mem_access (gimple_stmt_iterator *gsi, location_t location,
   gimple_set_location (g, location);
   gsi_insert_after (gsi, g, GSI_NEW_STMT);
 
-  t = build2 (MEM_REF, shadow_type, gimple_assign_lhs (g),
-	      build_int_cst (shadow_ptr_type, 0));
-  g = gimple_build_assign (make_ssa_name (shadow_type), MEM_REF, t);
-  gimple_set_location (g, location);
-  gsi_insert_after (gsi, g, GSI_NEW_STMT);
+  if (!return_ptr)
+    {
+      t = build2 (MEM_REF, shadow_type, gimple_assign_lhs (g),
+		  build_int_cst (shadow_ptr_type, 0));
+      g = gimple_build_assign (make_ssa_name (shadow_type), MEM_REF, t);
+      gimple_set_location (g, location);
+      gsi_insert_after (gsi, g, GSI_NEW_STMT);
+    }
+
   return gimple_assign_lhs (g);
 }
 
@@ -1824,7 +1922,8 @@ instrument_derefs (gimple_stmt_iterator *iter, tree t,
 	{
 	  /* Automatic vars in the current function will be always
 	     accessible.  */
-	  if (decl_function_context (inner) == current_function_decl)
+	  if (decl_function_context (inner) == current_function_decl
+	      && !asan_sanitize_use_after_scope ())
 	    return;
 	}
       /* Always instrument external vars, they might be dynamically
@@ -2573,6 +2672,110 @@ asan_finish_file (void)
   flag_sanitize |= SANITIZE_ADDRESS;
 }
 
+static void
+asan_store_shadow_bytes (gimple_stmt_iterator *iter, location_t loc,
+			 tree shadow,
+			 unsigned HOST_WIDE_INT base_addr_offset,
+			 unsigned char value, unsigned bytes)
+{
+  tree shadow_ptr_type;
+
+  switch (bytes)
+    {
+    case 1:
+      shadow_ptr_type = shadow_ptr_types[0];
+      break;
+    case 2:
+      shadow_ptr_type = shadow_ptr_types[1];
+      break;
+    case 4:
+      shadow_ptr_type = shadow_ptr_types[2];
+      break;
+    default:
+      gcc_unreachable ();
+    }
+
+  unsigned HOST_WIDE_INT val = 0;
+  for (unsigned i = 0; i < bytes; ++i)
+    val |= (unsigned HOST_WIDE_INT) value << (BITS_PER_UNIT * i);
+
+  tree magic = build_int_cst (TREE_TYPE (shadow_ptr_type), val);
+
+  tree dest = build2 (MEM_REF, TREE_TYPE (shadow_ptr_type), shadow,
+		      build_int_cst (shadow_ptr_type, base_addr_offset));
+
+  gimple *g = gimple_build_assign (dest, magic);
+  gimple_set_location (g, loc);
+  gsi_insert_after (iter, g, GSI_NEW_STMT);
+}
+
+/* Expand the ASAN_MARK builtins.  */
+
+bool
+asan_expand_mark_ifn (gimple_stmt_iterator *iter)
+{
+  gimple *g = gsi_stmt (*iter);
+  location_t loc = gimple_location (g);
+  HOST_WIDE_INT flags = tree_to_shwi (gimple_call_arg (g, 0));
+  gcc_assert (flags < ASAN_MARK_LAST);
+  bool is_clobber = (flags & ASAN_MARK_CLOBBER) != 0;
+
+  tree base = gimple_call_arg (g, 1);
+  gcc_checking_assert (TREE_CODE (base) == ADDR_EXPR);
+  tree decl = TREE_OPERAND (base, 0);
+  gcc_checking_assert (TREE_CODE (decl) == VAR_DECL);
+  asan_handled_variables.add (decl);
+  tree len = gimple_call_arg (g, 2);
+
+  gcc_assert (tree_fits_shwi_p (len));
+  HOST_WIDE_INT size_in_bytes = tree_to_shwi (len);
+  gcc_assert (size_in_bytes);
+
+  g = gimple_build_assign (make_ssa_name (pointer_sized_int_node),
+				  NOP_EXPR, base);
+  gimple_set_location (g, loc);
+  gsi_replace (iter, g, false);
+  tree base_addr = gimple_assign_lhs (g);
+
+  if (size_in_bytes <= ASAN_PARAM_USE_AFTER_SCOPE_DIRECT_EMISSION_THRESHOLD)
+    {
+      unsigned HOST_WIDE_INT shadow_size
+	= get_shadow_memory_size (size_in_bytes);
+      char c = (char) is_clobber ? ASAN_STACK_MAGIC_USE_AFTER_SCOPE : 0;
+
+      tree shadow = build_shadow_mem_access (iter, loc, base_addr,
+					     shadow_ptr_types[0], true);
+
+      for (unsigned HOST_WIDE_INT offset = 0; offset < shadow_size;)
+	{
+	  unsigned size = 1;
+	  if (shadow_size - offset >= 4)
+	    size = 4;
+	  else if (shadow_size - offset >= 2)
+	    size = 2;
+
+	  asan_store_shadow_bytes (iter, loc, shadow, offset, c, size);
+	  offset += size;
+	}
+    }
+  else
+    {
+      g = gimple_build_assign (make_ssa_name (pointer_sized_int_node),
+			       NOP_EXPR, len);
+      gimple_set_location (g, loc);
+      gsi_insert_before (iter, g, GSI_SAME_STMT);
+      tree sz_arg = gimple_assign_lhs (g);
+
+      tree fun = builtin_decl_implicit (is_clobber ? BUILT_IN_ASAN_CLOBBER_N
+					: BUILT_IN_ASAN_UNCLOBBER_N);
+      g = gimple_build_call (fun, 2, base_addr, sz_arg);
+      gimple_set_location (g, loc);
+      gsi_insert_after (iter, g, GSI_NEW_STMT);
+    }
+
+  return false;
+}
+
 /* Expand the ASAN_{LOAD,STORE} builtins.  */
 
 bool
diff --git a/gcc/asan.h b/gcc/asan.h
index 7ec693f..a38df9e 100644
--- a/gcc/asan.h
+++ b/gcc/asan.h
@@ -29,6 +29,7 @@ extern bool asan_protect_global (tree);
 extern void initialize_sanitizer_builtins (void);
 extern tree asan_dynamic_init_call (bool);
 extern bool asan_expand_check_ifn (gimple_stmt_iterator *, bool);
+extern bool asan_expand_mark_ifn (gimple_stmt_iterator *);
 
 extern gimple_stmt_iterator create_cond_insert_point
      (gimple_stmt_iterator *, bool, bool, bool, basic_block *, basic_block *);
@@ -50,15 +51,33 @@ extern alias_set_type asan_shadow_set;
    the frame.  Middle is for padding in between variables, right is
    above the last protected variable and partial immediately after variables
    up to ASAN_RED_ZONE_SIZE alignment.  */
-#define ASAN_STACK_MAGIC_LEFT		0xf1
-#define ASAN_STACK_MAGIC_MIDDLE		0xf2
-#define ASAN_STACK_MAGIC_RIGHT		0xf3
-#define ASAN_STACK_MAGIC_PARTIAL	0xf4
-#define ASAN_STACK_MAGIC_USE_AFTER_RET	0xf5
+#define ASAN_STACK_MAGIC_LEFT		  0xf1
+#define ASAN_STACK_MAGIC_MIDDLE		  0xf2
+#define ASAN_STACK_MAGIC_RIGHT		  0xf3
+#define ASAN_STACK_MAGIC_PARTIAL	  0xf4
+#define ASAN_STACK_MAGIC_USE_AFTER_RET	  0xf5
+#define ASAN_STACK_MAGIC_USE_AFTER_SCOPE  0xf8
 
 #define ASAN_STACK_FRAME_MAGIC		0x41b58ab3
 #define ASAN_STACK_RETIRED_MAGIC	0x45e0360e
 
+/* Various flags for Asan builtins.  */
+enum asan_check_flags
+{
+  ASAN_CHECK_STORE = 1 << 0,
+  ASAN_CHECK_SCALAR_ACCESS = 1 << 1,
+  ASAN_CHECK_NON_ZERO_LEN = 1 << 2,
+  ASAN_CHECK_LAST = 1 << 3
+};
+
+/* Flags for Asan check builtins.  */
+enum asan_mark_flags
+{
+  ASAN_MARK_CLOBBER = 1 << 0,
+  ASAN_MARK_UNCLOBBER = 1 << 1,
+  ASAN_MARK_LAST = 1 << 2
+};
+
 /* Return true if DECL should be guarded on the stack.  */
 
 static inline bool
@@ -105,4 +124,21 @@ asan_intercepted_p (enum built_in_function fcode)
 	 || fcode == BUILT_IN_STRNCMP
 	 || fcode == BUILT_IN_STRNCPY;
 }
+
+/* Return TRUE if we should instrument for use-after-scope sanity checking.  */
+
+static inline bool
+asan_sanitize_use_after_scope (void)
+{
+  return ((flag_sanitize & SANITIZE_ADDRESS_USE_AFTER_SCOPE)
+	  == SANITIZE_ADDRESS_USE_AFTER_SCOPE);
+}
+
+static inline bool
+asan_no_sanitize_address_p (void)
+{
+  return lookup_attribute ("no_sanitize_address",
+			   DECL_ATTRIBUTES (current_function_decl));
+}
+
 #endif /* TREE_ASAN */
diff --git a/gcc/cfgexpand.c b/gcc/cfgexpand.c
index 77a1964..262226f 100644
--- a/gcc/cfgexpand.c
+++ b/gcc/cfgexpand.c
@@ -876,8 +876,7 @@ asan_sanitize_stack_p (void)
 {
   return ((flag_sanitize & SANITIZE_ADDRESS)
 	  && ASAN_STACK
-	  && !lookup_attribute ("no_sanitize_address",
-				DECL_ATTRIBUTES (current_function_decl)));
+	  && !asan_no_sanitize_address_p ());
 }
 
 /* A subroutine of expand_used_vars.  Binpack the variables into
@@ -941,7 +940,8 @@ partition_stack_vars (void)
 	     sizes, as the shorter vars wouldn't be adequately protected.
 	     Don't do that for "large" (unsupported) alignment objects,
 	     those aren't protected anyway.  */
-	  if (asan_sanitize_stack_p () && isize != jsize
+	  if ((asan_sanitize_stack_p () || asan_sanitize_use_after_scope ())
+	      && isize != jsize
 	      && ialign * BITS_PER_UNIT <= MAX_SUPPORTED_STACK_ALIGNMENT)
 	    break;
 
@@ -1132,7 +1132,8 @@ expand_stack_vars (bool (*pred) (size_t), struct stack_vars_data *data)
       if (alignb * BITS_PER_UNIT <= MAX_SUPPORTED_STACK_ALIGNMENT)
 	{
 	  base = virtual_stack_vars_rtx;
-	  if (asan_sanitize_stack_p () && pred)
+	  if ((asan_sanitize_stack_p () || asan_sanitize_use_after_scope ())
+	      && pred)
 	    {
 	      HOST_WIDE_INT prev_offset
 		= align_base (frame_offset,
@@ -1503,7 +1504,9 @@ defer_stack_allocation (tree var, bool toplevel)
   /* If stack protection is enabled, *all* stack variables must be deferred,
      so that we can re-order the strings to the top of the frame.
      Similarly for Address Sanitizer.  */
-  if (flag_stack_protect || asan_sanitize_stack_p ())
+  if (flag_stack_protect
+      || asan_sanitize_stack_p ()
+      || asan_sanitize_use_after_scope ())
     return true;
 
   unsigned int align = TREE_CODE (var) == SSA_NAME
@@ -2203,7 +2206,7 @@ expand_used_vars (void)
 	    expand_stack_vars (stack_protect_decl_phase_2, &data);
 	}
 
-      if (asan_sanitize_stack_p ())
+      if (asan_sanitize_stack_p () || asan_sanitize_use_after_scope ())
 	/* Phase 3, any partitions that need asan protection
 	   in addition to phase 1 and 2.  */
 	expand_stack_vars (asan_decl_phase_3, &data);
diff --git a/gcc/flag-types.h b/gcc/flag-types.h
index dd57e16..ca1399d 100644
--- a/gcc/flag-types.h
+++ b/gcc/flag-types.h
@@ -229,6 +229,7 @@ enum sanitize_code {
   SANITIZE_OBJECT_SIZE = 1UL << 20,
   SANITIZE_VPTR = 1UL << 21,
   SANITIZE_BOUNDS_STRICT = 1UL << 22,
+  SANITIZE_USE_AFTER_SCOPE = 1UL << 23,
   SANITIZE_UNDEFINED = SANITIZE_SHIFT | SANITIZE_DIVIDE | SANITIZE_UNREACHABLE
 		       | SANITIZE_VLA | SANITIZE_NULL | SANITIZE_RETURN
 		       | SANITIZE_SI_OVERFLOW | SANITIZE_BOOL | SANITIZE_ENUM
@@ -237,7 +238,9 @@ enum sanitize_code {
 		       | SANITIZE_RETURNS_NONNULL_ATTRIBUTE
 		       | SANITIZE_OBJECT_SIZE | SANITIZE_VPTR,
   SANITIZE_NONDEFAULT = SANITIZE_FLOAT_DIVIDE | SANITIZE_FLOAT_CAST
-			| SANITIZE_BOUNDS_STRICT
+			| SANITIZE_BOUNDS_STRICT,
+  SANITIZE_ADDRESS_USE_AFTER_SCOPE = SANITIZE_ADDRESS
+			| SANITIZE_USE_AFTER_SCOPE
 };
 
 /* flag_vtable_verify initialization levels. */
diff --git a/gcc/gimplify.c b/gcc/gimplify.c
index c433a84..5db69e2 100644
--- a/gcc/gimplify.c
+++ b/gcc/gimplify.c
@@ -59,6 +59,11 @@ along with GCC; see the file COPYING3.  If not see
 #include "gimple-walk.h"
 #include "langhooks-def.h"	/* FIXME: for lhd_set_decl_assembler_name */
 #include "builtins.h"
+#include "params.h"
+#include "asan.h"
+
+/* Hash set of poisoned variables in a bind expr.  */
+static hash_set<tree> asan_poisoned_variables (13);
 
 enum gimplify_omp_var_data
 {
@@ -1087,6 +1092,29 @@ build_stack_save_restore (gcall **save, gcall **restore)
 			 1, tmp_var);
 }
 
+/* Generate IFN_ASAN_MARK internal call that depending on POISON flag
+   either poisons or unpoisons a DECL.  Created statement is appended
+   to SEQ_P gimple sequence.  */
+
+static void
+asan_poison_variable (tree decl, bool poison, gimple_seq *seq_p)
+{
+  /* When within an OMP context, do not emit ASAN_MARK internal fns.  */
+  if (gimplify_omp_ctxp)
+    return;
+
+  tree unit_size = DECL_SIZE_UNIT (decl);
+  tree base = build_fold_addr_expr (decl);
+
+  HOST_WIDE_INT flags = poison ? ASAN_MARK_CLOBBER : ASAN_MARK_UNCLOBBER;
+
+  gimple *g
+    = gimple_build_call_internal (IFN_ASAN_MARK, 3,
+				  build_int_cst (integer_type_node, flags),
+				  base, unit_size);
+  gimplify_seq_add_stmt (seq_p, g);
+}
+
 /* Gimplify a BIND_EXPR.  Just voidify and recurse.  */
 
 static enum gimplify_status
@@ -1189,6 +1217,11 @@ gimplify_bind_expr (tree *expr_p, gimple_seq *pre_p)
   /* Add clobbers for all variables that go out of scope.  */
   for (t = BIND_EXPR_VARS (bind_expr); t ; t = DECL_CHAIN (t))
     {
+      bool unpoison_var = asan_poisoned_variables.contains (t);
+      if (asan_sanitize_use_after_scope ()
+	  && unpoison_var)
+	asan_poisoned_variables.remove (t);
+
       if (TREE_CODE (t) == VAR_DECL
 	  && !is_global_var (t)
 	  && DECL_CONTEXT (t) == current_function_decl
@@ -1197,17 +1230,26 @@ gimplify_bind_expr (tree *expr_p, gimple_seq *pre_p)
 	  && !DECL_HAS_VALUE_EXPR_P (t)
 	  /* Only care for variables that have to be in memory.  Others
 	     will be rewritten into SSA names, hence moved to the top-level.  */
-	  && !is_gimple_reg (t)
-	  && flag_stack_reuse != SR_NONE)
+	  && !is_gimple_reg (t))
 	{
-	  tree clobber = build_constructor (TREE_TYPE (t), NULL);
-	  gimple *clobber_stmt;
-	  TREE_THIS_VOLATILE (clobber) = 1;
-	  clobber_stmt = gimple_build_assign (t, clobber);
-	  gimple_set_location (clobber_stmt, end_locus);
-	  gimplify_seq_add_stmt (&cleanup, clobber_stmt);
-
-	  if (flag_openacc && oacc_declare_returns != NULL)
+	  if (flag_stack_reuse != SR_NONE)
+	    {
+	      tree clobber = build_constructor (TREE_TYPE (t), NULL);
+	      gimple *clobber_stmt;
+	      TREE_THIS_VOLATILE (clobber) = 1;
+	      clobber_stmt = gimple_build_assign (t, clobber);
+	      gimple_set_location (clobber_stmt, end_locus);
+	      gimplify_seq_add_stmt (&cleanup, clobber_stmt);
+	    }
+
+	  if (asan_sanitize_use_after_scope ()
+	      && !asan_no_sanitize_address_p ()
+	      && unpoison_var)
+	    asan_poison_variable (t, true, &cleanup);
+
+	  if (flag_stack_reuse != SR_NONE
+	      && flag_openacc
+	      && oacc_declare_returns != NULL)
 	    {
 	      tree *c = oacc_declare_returns->get (t);
 	      if (c != NULL)
@@ -1479,6 +1521,22 @@ gimplify_decl_expr (tree *stmt_p, gimple_seq *seq_p)
 				   STACK_CHECK_MAX_VAR_SIZE) > 0))
 	gimplify_vla_decl (decl, seq_p);
 
+      tree unit_size = DECL_SIZE_UNIT (decl);
+      if (asan_sanitize_use_after_scope ()
+	  && !asan_no_sanitize_address_p ()
+	  && TREE_CODE (unit_size) == INTEGER_CST
+	  && needs_to_live_in_memory (decl)
+	  && DECL_NAME (decl) != NULL
+	  && IDENTIFIER_POINTER (DECL_NAME (decl)) != NULL
+	  && !TREE_STATIC (decl))
+	{
+	  TREE_ADDRESSABLE (decl) = 1;
+	  DECL_GIMPLE_REG_P (decl) = 0;
+
+	  asan_poisoned_variables.add (decl);
+	  asan_poison_variable (decl, false, seq_p);
+	}
+
       /* Some front ends do not explicitly declare all anonymous
 	 artificial variables.  We compensate here by declaring the
 	 variables, though it would be better if the front ends would
@@ -10030,6 +10088,25 @@ gimplify_omp_ordered (tree expr, gimple_seq body)
   return gimple_build_omp_ordered (body, OMP_ORDERED_CLAUSES (expr));
 }
 
+/* Sort pair of VAR_DECLs A and B by DECL_UID.  */
+
+static int
+sort_by_decl_uid (const void *a, const void *b)
+{
+  const tree *t1 = (const tree *)a;
+  const tree *t2 = (const tree *)b;
+
+  int uid1 = DECL_UID (*t1);
+  int uid2 = DECL_UID (*t2);
+
+  if (uid1 < uid2)
+    return -1;
+  else if (uid1 > uid2)
+    return 1;
+  else
+    return 0;
+}
+
 /* Convert the GENERIC expression tree *EXPR_P to GIMPLE.  If the
    expression produces a value to be used as an operand inside a GIMPLE
    statement, the value will be stored back in *EXPR_P.  This value will
@@ -10540,6 +10617,23 @@ gimplify_expr (tree *expr_p, gimple_seq *pre_p, gimple_seq *post_p,
 		      == current_function_decl);
 	  gimplify_seq_add_stmt (pre_p,
 			  gimple_build_label (LABEL_EXPR_LABEL (*expr_p)));
+
+	  if (asan_sanitize_use_after_scope ()
+	      && !asan_no_sanitize_address_p ())
+	    {
+	      unsigned c = asan_poisoned_variables.elements ();
+	      auto_vec<tree> sorted_variables (c);
+
+	      for (hash_set<tree>::iterator it
+		   = asan_poisoned_variables.begin ();
+		   it != asan_poisoned_variables.end (); ++it)
+		sorted_variables.safe_push (*it);
+
+	      sorted_variables.qsort (sort_by_decl_uid);
+
+	      for (unsigned i = 0; i < sorted_variables.length (); ++i)
+		asan_poison_variable (sorted_variables[i], false, pre_p);
+	    }
 	  break;
 
 	case CASE_LABEL_EXPR:
@@ -11633,7 +11727,11 @@ gimplify_function_tree (tree fndecl)
       && !needs_to_live_in_memory (ret))
     DECL_GIMPLE_REG_P (ret) = 1;
 
+  gcc_checking_assert (!asan_sanitize_use_after_scope ()
+		       || asan_poisoned_variables.elements () == 0);
   bind = gimplify_body (fndecl, true);
+  gcc_checking_assert (!asan_sanitize_use_after_scope ()
+		       || asan_poisoned_variables.elements () == 0);
 
   /* The tree body of the function is no longer needed, replace it
      with the new GIMPLE body.  */
diff --git a/gcc/hash-set.h b/gcc/hash-set.h
index 4ef4eba..05b2a98 100644
--- a/gcc/hash-set.h
+++ b/gcc/hash-set.h
@@ -65,6 +65,13 @@ public:
       m_table.remove_elt_with_hash (k, Traits::hash (k));
     }
 
+  /* Clear all elements of the hash set.  */
+
+  void empty ()
+    {
+      m_table.empty ();
+    }
+
   /* Call the call back on each pair of key and value with the passed in
      arg.  */
 
diff --git a/gcc/internal-fn.c b/gcc/internal-fn.c
index c867ddc..680a2d3 100644
--- a/gcc/internal-fn.c
+++ b/gcc/internal-fn.c
@@ -235,6 +235,15 @@ expand_ASAN_CHECK (internal_fn, gcall *)
   gcc_unreachable ();
 }
 
+/* This should get expanded in the sanopt pass.  */
+
+static void
+expand_ASAN_MARK (internal_fn, gcall *)
+{
+  gcc_unreachable ();
+}
+
+
 /* This should get expanded in the tsan pass.  */
 
 static void
diff --git a/gcc/internal-fn.def b/gcc/internal-fn.def
index e729d85..81492ad 100644
--- a/gcc/internal-fn.def
+++ b/gcc/internal-fn.def
@@ -158,6 +158,7 @@ DEF_INTERNAL_FN (UBSAN_OBJECT_SIZE, ECF_LEAF | ECF_NOTHROW, NULL)
 DEF_INTERNAL_FN (ABNORMAL_DISPATCHER, ECF_NORETURN, NULL)
 DEF_INTERNAL_FN (BUILTIN_EXPECT, ECF_CONST | ECF_LEAF | ECF_NOTHROW, NULL)
 DEF_INTERNAL_FN (ASAN_CHECK, ECF_TM_PURE | ECF_LEAF | ECF_NOTHROW, ".R...")
+DEF_INTERNAL_FN (ASAN_MARK, ECF_TM_PURE | ECF_LEAF | ECF_NOTHROW, ".R..")
 DEF_INTERNAL_FN (ADD_OVERFLOW, ECF_CONST | ECF_LEAF | ECF_NOTHROW, NULL)
 DEF_INTERNAL_FN (SUB_OVERFLOW, ECF_CONST | ECF_LEAF | ECF_NOTHROW, NULL)
 DEF_INTERNAL_FN (MUL_OVERFLOW, ECF_CONST | ECF_LEAF | ECF_NOTHROW, NULL)
diff --git a/gcc/opts.c b/gcc/opts.c
index 63d41ca..bf5da1f 100644
--- a/gcc/opts.c
+++ b/gcc/opts.c
@@ -972,6 +972,18 @@ finish_options (struct gcc_options *opts, struct gcc_options *opts_set,
       opts->x_flag_aggressive_loop_optimizations = 0;
       opts->x_flag_strict_overflow = 0;
     }
+
+  /* Force -fstack-reuse=none in case -fsanitize=use-after-scope is enabled.  */
+  if (opts->x_flag_sanitize & SANITIZE_USE_AFTER_SCOPE)
+    {
+      if (opts->x_flag_stack_reuse != SR_NONE
+	  && opts_set->x_flag_stack_reuse != SR_NONE)
+	error_at (loc,
+		  "-fsanitize=use-after-scope requires "
+		  "-fstack-reuse=none option");
+
+      opts->x_flag_stack_reuse = SR_NONE;
+    }
 }
 
 #define LEFT_COLUMN	27
@@ -1443,6 +1455,8 @@ const struct sanitizer_opts_s sanitizer_opts[] =
 {
 #define SANITIZER_OPT(name, flags) { #name, flags, sizeof #name - 1 }
   SANITIZER_OPT (address, SANITIZE_ADDRESS | SANITIZE_USER_ADDRESS),
+  SANITIZER_OPT (use-after-scope, SANITIZE_ADDRESS | SANITIZE_USER_ADDRESS
+		 | SANITIZE_USE_AFTER_SCOPE),
   SANITIZER_OPT (kernel-address, SANITIZE_ADDRESS | SANITIZE_KERNEL_ADDRESS),
   SANITIZER_OPT (thread, SANITIZE_THREAD),
   SANITIZER_OPT (leak, SANITIZE_LEAK),
diff --git a/gcc/params.def b/gcc/params.def
index 62a1e40..0c63989 100644
--- a/gcc/params.def
+++ b/gcc/params.def
@@ -1144,6 +1144,12 @@ DEFPARAM (PARAM_ASAN_INSTRUMENTATION_WITH_CALL_THRESHOLD,
          "in function becomes greater or equal to this number.",
          7000, 0, INT_MAX)
 
+DEFPARAM (PARAM_USE_AFTER_SCOPE_DIRECT_EMISSION_THRESHOLD,
+	 "use-after-scope-direct-emission-threshold",
+	 "Use direct poisoning/unpoisoning intructions for variables "
+	 "smaller or equal to this number.",
+         256, 0, INT_MAX)
+
 DEFPARAM (PARAM_UNINIT_CONTROL_DEP_ATTEMPTS,
 	  "uninit-control-dep-attempts",
 	  "Maximum number of nested calls to search for control dependencies "
diff --git a/gcc/params.h b/gcc/params.h
index 7221ab6..f8bd022 100644
--- a/gcc/params.h
+++ b/gcc/params.h
@@ -243,5 +243,7 @@ extern void init_param_values (int *params);
   PARAM_VALUE (PARAM_ASAN_USE_AFTER_RETURN)
 #define ASAN_INSTRUMENTATION_WITH_CALL_THRESHOLD \
   PARAM_VALUE (PARAM_ASAN_INSTRUMENTATION_WITH_CALL_THRESHOLD)
+#define ASAN_PARAM_USE_AFTER_SCOPE_DIRECT_EMISSION_THRESHOLD \
+  PARAM_VALUE (PARAM_USE_AFTER_SCOPE_DIRECT_EMISSION_THRESHOLD)
 
 #endif /* ! GCC_PARAMS_H */
diff --git a/gcc/sanitizer.def b/gcc/sanitizer.def
index 303c1e4..1c142e9 100644
--- a/gcc/sanitizer.def
+++ b/gcc/sanitizer.def
@@ -165,6 +165,10 @@ DEF_SANITIZER_BUILTIN(BUILT_IN_ASAN_BEFORE_DYNAMIC_INIT,
 DEF_SANITIZER_BUILTIN(BUILT_IN_ASAN_AFTER_DYNAMIC_INIT,
 		      "__asan_after_dynamic_init",
 		      BT_FN_VOID, ATTR_NOTHROW_LEAF_LIST)
+DEF_SANITIZER_BUILTIN(BUILT_IN_ASAN_CLOBBER_N, "__asan_poison_stack_memory",
+		      BT_FN_VOID_PTR_PTRMODE, 0)
+DEF_SANITIZER_BUILTIN(BUILT_IN_ASAN_UNCLOBBER_N, "__asan_unpoison_stack_memory",
+		      BT_FN_VOID_PTR_PTRMODE, 0)
 
 /* Thread Sanitizer */
 DEF_SANITIZER_BUILTIN(BUILT_IN_TSAN_INIT, "__tsan_init", 
diff --git a/gcc/sanopt.c b/gcc/sanopt.c
index 2660453..4436705 100644
--- a/gcc/sanopt.c
+++ b/gcc/sanopt.c
@@ -704,6 +704,9 @@ pass_sanopt::execute (function *fun)
 		case IFN_ASAN_CHECK:
 		  no_next = asan_expand_check_ifn (&gsi, use_calls);
 		  break;
+		case IFN_ASAN_MARK:
+		  no_next = asan_expand_mark_ifn (&gsi);
+		  break;
 		default:
 		  break;
 		}
-- 
2.8.2

Reply via email to