[PATCH 6/X] libsanitizer: Add hwasan pass and associated gimple changes

Richard Sandiford richard.sandiford@arm.com
Wed Oct 14 18:37:15 GMT 2020


Matthew Malcomson <matthew.malcomson@arm.com> writes:
> @@ -133,6 +137,13 @@ enum asan_mark_flags
>  #undef DEF
>  };
>  
> +enum hwasan_mark_flags
> +{
> +#define DEF(X) HWASAN_MARK_##X
> +  IFN_ASAN_MARK_FLAGS
> +#undef DEF
> +};

Are these used anywhere?  It looks like expand_HWASAN_MARK uses the
plain asan versions.

> @@ -640,6 +684,85 @@ handle_builtin_alloca (gcall *call, gimple_stmt_iterator *iter)
>      = DECL_FUNCTION_CODE (callee) == BUILT_IN_ALLOCA
>        ? 0 : tree_to_uhwi (gimple_call_arg (call, 1));
>  
> +  if (hwasan_sanitize_allocas_p ())
> +    {
> +      /*
> +	 HWASAN needs a different expansion.
> +
> +	 addr = __builtin_alloca (size, align);
> +
> +	 should be replaced by
> +
> +	 new_size = size rounded up to HWASAN_TAG_GRANULE_SIZE byte alignment;
> +	 untagged_addr = __builtin_alloca (new_size, align);
> +	 tag = __hwasan_choose_alloca_tag ();
> +	 addr = __hwasan_tag_pointer (untagged_addr, tag);
> +	 __hwasan_tag_memory (untagged_addr, tag, new_size);
> +	*/
> +      /* Ensure alignment at least HWASAN_TAG_GRANULE_SIZE bytes so we start on
> +	 a tag granule.  */
> +      align = align > HWASAN_TAG_GRANULE_SIZE ? align : HWASAN_TAG_GRANULE_SIZE;
> +
> +      uint8_t tg_mask = HWASAN_TAG_GRANULE_SIZE - 1;
> +      /* tree new_size = (old_size + tg_mask) & ~tg_mask;  */
> +      tree old_size = gimple_call_arg (call, 0);
> +      tree tree_mask = build_int_cst (size_type_node, tg_mask);
> +      g = gimple_build_assign (make_ssa_name (size_type_node), PLUS_EXPR,
> +			       old_size, tree_mask);
> +      gsi_insert_before (iter, g, GSI_SAME_STMT);
> +      tree oversize = gimple_assign_lhs (g);
> +
> +      g = gimple_build_assign (make_ssa_name (size_type_node), BIT_NOT_EXPR,
> +			       tree_mask);
> +      tree mask = gimple_assign_lhs (g);
> +      gsi_insert_before (iter, g, GSI_SAME_STMT);

Seems simpler to use:

      tree mask = build_int_cst (size_type_node, -HWASAN_TAG_GRANULE_SIZE);

> +
> +      g = gimple_build_assign (make_ssa_name (size_type_node), BIT_AND_EXPR,
> +			       oversize, mask);
> +      gsi_insert_before (iter, g, GSI_SAME_STMT);
> +      tree new_size = gimple_assign_lhs (g);
> +
> +      /* emit the alloca call */
> +      tree fn = builtin_decl_implicit (BUILT_IN_ALLOCA_WITH_ALIGN);
> +      gg = gimple_build_call (fn, 2, new_size,
> +			      build_int_cst (size_type_node, align));
> +      tree untagged_addr = make_ssa_name (ptr_type, gg);
> +      gimple_call_set_lhs (gg, untagged_addr);
> +      gsi_insert_before (iter, gg, GSI_SAME_STMT);
> +
> +      /* Insert code choosing the tag.
> +	 Here we use an internal function so we can choose the tag at expand
> +	 time.  We need the decision to be made after stack variables have been
> +	 assigned their tag (i.e. once the tag_offset variable has been set to
> +	 one after the last stack variables tag).  */
> +
> +      gg = gimple_build_call_internal (IFN_HWASAN_CHOOSE_TAG, 0);
> +      tree tag = make_ssa_name (unsigned_char_type_node, gg);
> +      gimple_call_set_lhs (gg, tag);
> +      gsi_insert_before (iter, gg, GSI_SAME_STMT);
> +
> +      /* Insert code adding tag to pointer.  */
> +      fn = builtin_decl_implicit (BUILT_IN_HWASAN_TAG_PTR);
> +      gg = gimple_build_call (fn, 2, untagged_addr, tag);
> +      tree addr = make_ssa_name (ptr_type, gg);
> +      gimple_call_set_lhs (gg, addr);
> +      gsi_insert_before (iter, gg, GSI_SAME_STMT);
> +
> +      /* Insert code tagging shadow memory.
> +	 NOTE: require using `untagged_addr` here for libhwasan API.  */
> +      fn = builtin_decl_implicit (BUILT_IN_HWASAN_TAG_MEM);
> +      gg = gimple_build_call (fn, 3, untagged_addr, tag, new_size);
> +      gsi_insert_before (iter, gg, GSI_SAME_STMT);
> +
> +      /* Finally, replace old alloca ptr with NEW_ALLOCA.  */
> +      replace_call_with_value (iter, addr);
> +      return;
> +    }
> +
> +  tree last_alloca = get_last_alloca_addr ();
> +  const HOST_WIDE_INT redzone_mask = ASAN_RED_ZONE_SIZE - 1;
> +
> +
>    /* If ALIGN > ASAN_RED_ZONE_SIZE, we embed left redzone into first ALIGN
>       bytes of allocated space.  Otherwise, align alloca to ASAN_RED_ZONE_SIZE
>       manually.  */
> @@ -792,6 +915,31 @@ get_mem_refs_of_builtin_call (gcall *call,
>        break;
>  
>      case BUILT_IN_STRLEN:
> +      /* Special case strlen here since its length is taken from its return
> +	 value.
> +
> +	 The approach taken by the sanitizers is to check a memory access
> +	 before it's taken.  For ASAN strlen is intercepted by libasan, so no
> +	 check is inserted by the compiler.
> +
> +	 This function still returns `true` and provides a length to the rest
> +	 of the ASAN pass in order to record what areas have been checked,
> +	 avoiding superfluous checks later on.
> +
> +	 HWASAN does not intercept any of these internal functions.
> +	 This means that checks for memory accesses must be inserted by the
> +	 compiler.
> +	 strlen is a special case, because we can tell the length from the
> +	 return of the function, but that is not known until after the function
> +	 has returned.
> +
> +	 Hence we can't check the memory access before it happens.
> +	 We could check the memory access after it has already happened, but
> +	 for now I'm choosing to just ignore `strlen` calls.
> +	 This decision was simply made because that means the special case is
> +	 limited to this one case of this one function.  */
> +      if (hwasan_sanitize_p ())
> +	return false;

What does LLVM do here?

s/I'm choosing/we choose/

> @@ -1361,6 +1509,149 @@ asan_redzone_buffer::flush_if_full (void)
>      flush_redzone_payload ();
>  }
>  
> +
> +/* HWAddressSanitizer (hwasan) is a probabilistic method for detecting
> +   out-of-bounds and use-after-free bugs.
> +   Read more:
> +   http://code.google.com/p/address-sanitizer/
> +
> +   Similar to AddressSanitizer (asan) it consists of two parts: the
> +   instrumentation module in this file, and a run-time library.
> +
> +   The instrumentation module adds a run-time check before every memory insn in
> +   the same manner as asan (see the block comment for AddressSanitizer above).
> +   Currently, hwasan only adds out-of-line instrumentation, where each check is
> +   implemented as a function call to the run-time library.  Hence a check for a
> +   load of N bytes from address X would be implemented with a function call to
> +   __hwasan_loadN(X), and checking a store of N bytes from address X would be
> +   implemented with a function call to __hwasan_storeN(X).
> +
> +   The main difference between hwasan and asan is in the information stored to
> +   help this checking.  Both sanitizers use a shadow memory area which stores
> +   data recording the state of main memory at a corresponding address.
> +
> +   For hwasan, each 16 byte granule in main memory has a corresponding 1 byte
> +   in shadow memory.  This shadow address can be calculated with equation:
> +     (addr >> HWASAN_TAG_SHIFT_SIZE) + __hwasan_shadow_memory_dynamic_address;
> +   The conversion between real and shadow memory for asan is given in the block
> +   comment at the top of this file.
> +   The description of how this shadow memory is laid out for asan is in the

Maybe “is also in”?

> +   block comment at the top of this file, here we describe how this shadow
> +   memory is used for hwasan.
> +
> +   For hwasan, each variable is assigned a byte-sized 'tag'.  The extent of
> +   the shadow memory for that variable is filled with the assigned tag, and
> +   every pointer referencing that variable has its top byte set to the same
> +   tag.  The run-time library redefines malloc so that every allocation returns
> +   a tagged pointer and tags the corresponding shadow memory with the same tag.
> +
> +   On each pointer dereference the tag found in the pointer is compared to the
> +   tag found in the shadow memory corresponding to the accessed memory address.
> +   If these tags are found to differ then this memory access is judged to be
> +   invalid and a report is generated.
> +
> +   This method of bug detection is not perfect -- it can not catch every bad
> +   access -- but catches them probabilistically instead.  There is always the
> +   possibility that an invalid memory access will happen to access memory
> +   tagged with the same tag as the pointer that this access used.
> +   The chances of this are approx. 0.4% for any two uncorrelated objects.
> +
> +   Random tag generation can mitigate this problem by decreasing the
> +   probability that an invalid access will be missed in the same manner over
> +   multiple runs.  i.e. if two objects are tagged the same in one run of the
> +   binary they are unlikely to be tagged the same in the next run.
> +   Both heap and stack allocated objects have random tags by default.
> +
> +   [16 byte granule implications]
> +    Since the shadow memory only has a resolution on real memory of 16 bytes,
> +    invalid accesses that are within the same 16 byte granule as a valid
> +    address will not be caught.
> +
> +    There is a "short-granule" feature in the runtime library which does catch
> +    such accesses, but this feature is not implemented for stack objects (since
> +    stack objects are allocated and tagged by compiler instrumentation, and
> +    this feature has not yet been implemented in GCC instrumentation).
> +
> +    Another outcome of this 16 byte resolution is that each tagged object must
> +    be 16 byte aligned.  If two objects were to share any 16 byte granule in
> +    memory, then they both would have to be given the same tag, and invalid
> +    accesses to one using a pointer to the other would be undetectable.
> +
> +   [Compiler instrumentation]
> +    Compiler instrumentation ensures that two adjacent buffers on the stack are
> +    given different tags, this means an access to one buffer using a pointer
> +    generated from the other (e.g. through buffer overrun) will have mismatched
> +    tags and be caught by hwasan.
> +
> +    We don't randomly tag every object on the stack, since that would require
> +    keeping many registers to record each tag.  Instead we randomly generate a
> +    tag for each function frame, and each new stack object uses a tag offset
> +    from that frame tag.
> +    i.e. each object is tagged as RFT + offset, where RFT is the "random frame
> +    tag" generated for this frame.

So randomisation isn't effective at perturbing the pairs of mismatching
tags within a frame, but that is mitigated by objects with the same tag
within a frame being very far apart.  Is that right?

> +    As a demonstration, using the same example program as in the asan block
> +    comment above:
> +
> +     int
> +     foo ()
> +     {
> +       char a[23] = {0};
> +       int b[2] = {0};
> +
> +       a[5] = 1;
> +       b[1] = 2;
> +
> +       return a[5] + b[1];
> +     }
> +
> +    On AArch64 the stack will be ordered as follows for the above function:
> +
> +    Slot 1/ [24 bytes for variable 'a']
> +    Slot 2/ [8 bytes padding for alignment]
> +    Slot 3/ [8 bytes for variable 'b']
> +    Slot 4/ [8 bytes padding for alignment]
> +
> +    (The padding is there to ensure 16 byte alignment as described in the 16
> +     byte granule implications).
> +
> +    While the shadow memory will be ordered as follows:
> +
> +    - 2 bytes (representing 32 bytes in real memory) tagged with RFT + 1.
> +    - 1 byte (representing 16 bytes in real memory) tagged with RFT + 2.
> +
> +    And any pointer to "a" will have the tag RFT + 1, and any pointer to "b"
> +    will have the tag RFT + 2.
> +
> +   [Top Byte Ignore requirements]
> +    Hwasan requires the ability to store an 8 bit tag in every pointer.  There
> +    is no instrumentation done to remove this tag from pointers before
> +    dereferencing, which means the hardware must ignore this tag during memory
> +    accesses.
> +
> +    One architecture that provides this feature is the AArch64 architecture.
> +    This is the only architecture that hwasan is currently implemented for.

This could easily get out of date.  Maybe it would be better to refer to
the target hook instead.

> +
> +   [Stack requires cleanup on unwinding]
> +    During normal operation of a hwasan sanitized program more space in the
> +    shadow memory becomes tagged as the stack grows.  As the stack shrinks this
> +    shadow memory space must become untagged.  If it is not untagged then when
> +    the stack grows again (during other function calls later on in the program)
> +    objects on the stack that are usually not tagged (e.g. parameters passed on
> +    the stack) can be placed in memory whose shadow space is tagged with
> +    something else, and accesses can cause false positive reports.
> +
> +    Hence we place untagging code on every epilogue of functions which tag some
> +    stack objects.
> +
> +    Moreover, the run-time library intercepts longjmp & setjmp to uncolour

uncolor

> +    when the stack is unwound this way.
> +
> +    C++ exceptions are not yet handled, which means this sanitizer can not
> +    handle C++ code that throws exceptions -- it will give false positives
> +    after an exception has been thrown.  */

Is this a limitation in the library as well as the compiler side?
Might be worth clarifying in the comment.

But thanks for this, it's a really nice write-up.

> @@ -2301,7 +2620,8 @@ instrument_derefs (gimple_stmt_iterator *iter, tree t,
>      {
>        if (DECL_THREAD_LOCAL_P (inner))
>  	return;
> -      if (!param_asan_globals && is_global_var (inner))
> +      if ((hwasan_sanitize_p () || !param_asan_globals)
> +	  && is_global_var (inner))
>          return;
>        if (!TREE_STATIC (inner))
>  	{

Could you expand on this?

> @@ -2530,10 +2850,13 @@ maybe_instrument_call (gimple_stmt_iterator *iter)
>  	      break;
>  	    }
>  	}
> -      tree decl = builtin_decl_implicit (BUILT_IN_ASAN_HANDLE_NO_RETURN);
> -      gimple *g = gimple_build_call (decl, 0);
> -      gimple_set_location (g, gimple_location (stmt));
> -      gsi_insert_before (iter, g, GSI_SAME_STMT);
> +      if (! hwasan_sanitize_p ())
> +	{
> +	  tree decl = builtin_decl_implicit (BUILT_IN_ASAN_HANDLE_NO_RETURN);
> +	  gimple *g = gimple_build_call (decl, 0);
> +	  gimple_set_location (g, gimple_location (stmt));
> +	  gsi_insert_before (iter, g, GSI_SAME_STMT);
> +	}

Why is this only needed for asan?  (Just curious.)

> @@ -3255,18 +3581,34 @@ asan_expand_mark_ifn (gimple_stmt_iterator *iter)
>  
>    gcc_checking_assert (TREE_CODE (decl) == VAR_DECL);
>  
> +  tree len = gimple_call_arg (g, 2);
> +  gcc_assert (tree_fits_shwi_p (len));
> +  unsigned HOST_WIDE_INT size_in_bytes = tree_to_shwi (len);
> +  gcc_assert (size_in_bytes);
> +
> +  if (hwasan_sanitize_p ())
> +    {
> +      gcc_assert (param_hwasan_instrument_stack);
> +      /* Here we swap ASAN_MARK calls for HWASAN_MARK.
> +	 This is because we are using the approach of using ASAN_MARK as a
> +	 synonym until here.
> +	 That approach means we don't yet have to duplicate all the special
> +	 cases for ASAN_MARK and ASAN_POISON with the exact same handling but
> +	 called HWASAN_MARK etc.  */

Definitely in favour of that :-)

> +      gimple *hw_poison_call
> +	= gimple_build_call_internal (IFN_HWASAN_MARK, 3,
> +				      gimple_call_arg (g, 0),
> +				      base, len);
> +      gsi_replace (iter, hw_poison_call, false);
> +      return false;
> +    }
> +
>    if (is_poison)
>      {
>        if (asan_handled_variables == NULL)
>  	asan_handled_variables = new hash_set<tree> (16);
>        asan_handled_variables->add (decl);
>      }
> -  tree len = gimple_call_arg (g, 2);
> -
> -  gcc_assert (tree_fits_shwi_p (len));
> -  unsigned 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);
> @@ -3329,6 +3671,7 @@ asan_expand_mark_ifn (gimple_stmt_iterator *iter)
>  bool
>  asan_expand_check_ifn (gimple_stmt_iterator *iter, bool use_calls)
>  {
> +  gcc_assert (!hwasan_sanitize_p ());
>    gimple *g = gsi_stmt (*iter);
>    location_t loc = gimple_location (g);
>    bool recover_p;
> @@ -3602,11 +3945,66 @@ asan_expand_poison_ifn (gimple_stmt_iterator *iter,
>  
>        int nargs;
>        bool store_p = gimple_call_internal_p (use, IFN_ASAN_POISON_USE);
> -      tree fun = report_error_func (store_p, recover_p, tree_to_uhwi (size),
> -				    &nargs);
> -
> -      gcall *call = gimple_build_call (fun, 1,
> -				       build_fold_addr_expr (shadow_var));
> +      gcall *call;
> +      if (hwasan_sanitize_p ())
> +	{
> +	  tree fun = builtin_decl_implicit (BUILT_IN_HWASAN_TAG_MISMATCH4);
> +	  /* NOTE: hwasan has no __hwasan_report_* functions like asan does.
> +		We use __hwasan_tag_mismatch4 with arguments that tell it the
> +		size of access and load to report all tag mismatches.
> +
> +		The arguments to this function are:
> +		  Address of invalid access.
> +		  Bitfield containing information about the access
> +		    (access_info)
> +		  Pointer to a frame of registers
> +		    (for use in printing the contents of registers in a dump)
> +		    Not used yet -- to be used by inline instrumentation.
> +		  Size of access (only if size is not representable in the
> +		    access_info argument).
> +
> +		The access_info bitfield encodes the following pieces of
> +		information:
> +		  - Is this a store or load?
> +		    access_info & 0x10  =>  store
> +		  - Should the program continue after reporting the error?
> +		    access_info & 0x20  =>  recover
> +		  - What size access is this (if size is less than 16 bytes)
> +		    if (access_info & 0xf == 0xf)
> +		      size is taken from last argument.
> +		    else
> +		      size == 1 << (access_info & 0xf)
> +
> +		The last argument contains the size of the access iff the
> +		access was of a size greater than or equal to 16 bytes.
> +
> +		See the function definition `__hwasan_tag_mismatch4` in
> +		libsanitizer/hwasan for the full definition.
> +		*/
> +	  unsigned HOST_WIDE_INT size_in_bytes = tree_to_uhwi (size);
> +	  unsigned size_indicator = (size_in_bytes > 16)
> +	    ? 0xf
> +	    : exact_log2 (size_in_bytes);

The parts about “16 bytes” don't seem to match the equation;
seems like it's a power of 2 in the range [1, 16384].

Is there any benefit to using the size field encoded in the access_info here?
AIUI it was retained for compatibility with pre-__hwasan_tag_mismatch4
versions of the library.  Seems like we could always use 0xf instead.

> +	  unsigned access_info = (0x20 * recover_p)
> +	    + (0x10 * store_p)
> +	    + (size_indicator);
> +	  tree long_pointer_type
> +	    = build_pointer_type (long_unsigned_type_node);

pointer_sized_int_type seems more appropriate, given that the second
argument and the target of the third argument are uptrs.

> +	  call = gimple_build_call (fun, 3,
> +				    build_fold_addr_expr (shadow_var),
> +				    build_int_cst (long_unsigned_type_node,
> +						   access_info),
> +				    build_int_cst (long_pointer_type,
> +						   0),
> +				    size);
> +	}
> +      else
> +	{
> +	  tree fun = report_error_func (store_p, recover_p, tree_to_uhwi (size),
> +					&nargs);
> +	  call = gimple_build_call (fun, 1,
> +				    build_fold_addr_expr (shadow_var));
> +	}
>        gimple_set_location (call, gimple_location (use));
>        gimple *call_to_insert = call;
>  
> @@ -4005,4 +4425,216 @@ hwasan_finish_file (void)
>    flag_sanitize |= SANITIZE_HWADDRESS;
>  }
>  
> +/* Construct a function tree for __hwasan_{load,store}{1,2,4,8,16,_n}.
> +   IS_STORE is either 1 (for a store) or 0 (for a load).  */
> +static tree
> +hwasan_check_func (bool is_store, bool recover_p, HOST_WIDE_INT size_in_bytes,
> +		   int *nargs)
> +{
> +  static enum built_in_function check[2][2][6]
> +    = { { { BUILT_IN_HWASAN_LOAD1, BUILT_IN_HWASAN_LOAD2,
> +	    BUILT_IN_HWASAN_LOAD4, BUILT_IN_HWASAN_LOAD8,
> +	    BUILT_IN_HWASAN_LOAD16, BUILT_IN_HWASAN_LOADN },
> +	  { BUILT_IN_HWASAN_STORE1, BUILT_IN_HWASAN_STORE2,
> +	    BUILT_IN_HWASAN_STORE4, BUILT_IN_HWASAN_STORE8,
> +	    BUILT_IN_HWASAN_STORE16, BUILT_IN_HWASAN_STOREN } },
> +	{ { BUILT_IN_HWASAN_LOAD1_NOABORT,
> +	    BUILT_IN_HWASAN_LOAD2_NOABORT,
> +	    BUILT_IN_HWASAN_LOAD4_NOABORT,
> +	    BUILT_IN_HWASAN_LOAD8_NOABORT,
> +	    BUILT_IN_HWASAN_LOAD16_NOABORT,
> +	    BUILT_IN_HWASAN_LOADN_NOABORT },
> +	  { BUILT_IN_HWASAN_STORE1_NOABORT,
> +	    BUILT_IN_HWASAN_STORE2_NOABORT,
> +	    BUILT_IN_HWASAN_STORE4_NOABORT,
> +	    BUILT_IN_HWASAN_STORE8_NOABORT,
> +	    BUILT_IN_HWASAN_STORE16_NOABORT,
> +	    BUILT_IN_HWASAN_STOREN_NOABORT } } };
> +  if (size_in_bytes == -1)
> +    {
> +      *nargs = 2;
> +      return builtin_decl_implicit (check[recover_p][is_store][5]);
> +    }
> +  *nargs = 1;
> +  int size_log2 = exact_log2 (size_in_bytes);
> +  return builtin_decl_implicit (check[recover_p][is_store][size_log2]);

I don't follow the logic here.  Why is it the case that we have to
handle size_in_bytes == -1, but are otherwise guaranteed that
size_in_bytes is in [1, 2, 4, 8, 16]?  I.e. why is the tree_fits_shwi_p
call needed here:

> +  unsigned HOST_WIDE_INT size_in_bytes
> +    = is_scalar_access && tree_fits_shwi_p (len) ? tree_to_shwi (len) : -1;

?  If is_scalar_access guarantees a valid value then I think we should
drop the tree_fits_shwi_p check above and instead assert that size_log2
is in range.  If is_scalar_access doesn't guarantee a valid value then
I think we should check size_log2 and set *nargs to 2 for out-of-range
values.

> +}
> +
> +/* Expand the HWASAN_{LOAD,STORE} builtins.  */
> +bool
> +hwasan_expand_check_ifn (gimple_stmt_iterator *iter, bool)
> +{
> +  gimple *g = gsi_stmt (*iter);
> +  location_t loc = gimple_location (g);
> +  bool recover_p;
> +  if (flag_sanitize & SANITIZE_USER_HWADDRESS)
> +    recover_p = (flag_sanitize_recover & SANITIZE_USER_HWADDRESS) != 0;
> +  else
> +    recover_p = (flag_sanitize_recover & SANITIZE_KERNEL_HWADDRESS) != 0;
> +
> +  HOST_WIDE_INT flags = tree_to_shwi (gimple_call_arg (g, 0));
> +  gcc_assert (flags < ASAN_CHECK_LAST);
> +  bool is_scalar_access = (flags & ASAN_CHECK_SCALAR_ACCESS) != 0;
> +  bool is_store = (flags & ASAN_CHECK_STORE) != 0;
> +  bool is_non_zero_len = (flags & ASAN_CHECK_NON_ZERO_LEN) != 0;
> +
> +  tree base = gimple_call_arg (g, 1);
> +  tree len = gimple_call_arg (g, 2);
> +
> +  /* `align` is unused for HWASAN_CHECK, but I pass the argument anyway

s/I/we/.

> +     since that way the arguments match ASAN_CHECK.  */
> +  /* HOST_WIDE_INT align = tree_to_shwi (gimple_call_arg (g, 3));  */
> +
> +  unsigned HOST_WIDE_INT size_in_bytes
> +    = is_scalar_access && tree_fits_shwi_p (len) ? tree_to_shwi (len) : -1;
> +
> +  gimple_stmt_iterator gsi = *iter;
> +
> +  if (!is_non_zero_len)
> +    {
> +      /* So, the length of the memory area to hwasan-protect is
> +	 non-constant.  Let's guard the generated instrumentation code
> +	 like:
> +
> +	 if (len != 0)
> +	   {
> +	     // hwasan instrumentation code goes here.
> +	   }
> +	 // falltrough instructions, starting with *ITER.  */
> +
> +      g = gimple_build_cond (NE_EXPR,
> +			    len,
> +			    build_int_cst (TREE_TYPE (len), 0),
> +			    NULL_TREE, NULL_TREE);
> +      gimple_set_location (g, loc);
> +
> +      basic_block then_bb, fallthrough_bb;
> +      insert_if_then_before_iter (as_a <gcond *> (g), iter,
> +				  /*then_more_likely_p=*/true,
> +				  &then_bb, &fallthrough_bb);
> +      /* Note that fallthrough_bb starts with the statement that was
> +	pointed to by ITER.  */
> +
> +      /* The 'then block' of the 'if (len != 0) condition is where
> +	we'll generate the hwasan instrumentation code now.  */
> +      gsi = gsi_last_bb (then_bb);
> +    }
> +
> +  g = gimple_build_assign (make_ssa_name (pointer_sized_int_node),
> +			   NOP_EXPR, base);
> +  gimple_set_location (g, loc);
> +  gsi_insert_after (&gsi, g, GSI_NEW_STMT);
> +  tree base_addr = gimple_assign_lhs (g);
> +
> +  int nargs = 0;
> +  tree fun = hwasan_check_func (is_store, recover_p, size_in_bytes, &nargs);
> +  if (nargs == 1)
> +    g = gimple_build_call (fun, 1, base_addr);
> +  else
> +    {
> +      gcc_assert (nargs == 2);
> +      g = gimple_build_assign (make_ssa_name (pointer_sized_int_node),
> +			       NOP_EXPR, len);
> +      gimple_set_location (g, loc);
> +      gsi_insert_after (&gsi, g, GSI_NEW_STMT);
> +      tree sz_arg = gimple_assign_lhs (g);
> +      g = gimple_build_call (fun, nargs, base_addr, sz_arg);
> +    }
> +
> +  gimple_set_location (g, loc);
> +  gsi_insert_after (&gsi, g, GSI_NEW_STMT);
> +  gsi_remove (iter, true);
> +  *iter = gsi;
> +  return false;
> +}
> +
> +/* For stack tagging:
> +     Dummy: the HWASAN_MARK internal function should only ever be in the code
> +     after the sanopt pass.  */

Same nit as the previous part about this style of comment formatting.

> +bool
> +hwasan_expand_mark_ifn (gimple_stmt_iterator *)
> +{
> +  gcc_unreachable ();
> +}
> +
> +bool
> +gate_hwasan ()
> +{
> +  return hwasan_sanitize_p ();
> +}
> +
> +namespace {
> +
> +const pass_data pass_data_hwasan =
> +{
> +  GIMPLE_PASS, /* type */
> +  "hwasan", /* name */
> +  OPTGROUP_NONE, /* optinfo_flags */
> +  TV_NONE, /* tv_id */
> +  ( PROP_ssa | PROP_cfg | PROP_gimple_leh ), /* properties_required */
> +  0, /* properties_provided */
> +  0, /* properties_destroyed */
> +  0, /* todo_flags_start */
> +  TODO_update_ssa, /* todo_flags_finish */
> +};
> +
> +class pass_hwasan : public gimple_opt_pass
> +{
> +public:
> +  pass_hwasan (gcc::context *ctxt)
> +    : gimple_opt_pass (pass_data_hwasan, ctxt)
> +  {}
> +
> +  /* opt_pass methods: */
> +  opt_pass * clone () { return new pass_hwasan (m_ctxt); }
> +  virtual bool gate (function *) { return gate_hwasan (); }
> +  virtual unsigned int execute (function *) { return hwasan_instrument (); }
> +
> +}; /* class pass_hwasan  */
> +
> +} /* anon namespace  */
> +
> +gimple_opt_pass *
> +make_pass_hwasan (gcc::context *ctxt)
> +{
> +  return new pass_hwasan (ctxt);
> +}
> +
> +namespace {
> +
> +const pass_data pass_data_hwasan_O0 =
> +{
> +  GIMPLE_PASS, /* type */
> +  "hwasan_O0", /* name */
> +  OPTGROUP_NONE, /* optinfo_flags */
> +  TV_NONE, /* tv_id */
> +  ( PROP_ssa | PROP_cfg | PROP_gimple_leh ), /* properties_required */
> +  0, /* properties_provided */
> +  0, /* properties_destroyed */
> +  0, /* todo_flags_start */
> +  TODO_update_ssa, /* todo_flags_finish */
> +};
> +
> +class pass_hwasan_O0 : public gimple_opt_pass
> +{
> +public:
> +  pass_hwasan_O0 (gcc::context *ctxt)
> +    : gimple_opt_pass (pass_data_hwasan_O0, ctxt)
> +  {}
> +
> +  /* opt_pass methods: */
> +  opt_pass * clone () { return new pass_hwasan_O0 (m_ctxt); }
> +  virtual bool gate (function *) { return !optimize && gate_hwasan (); }
> +  virtual unsigned int execute (function *) { return hwasan_instrument (); }
> +
> +}; /* class pass_hwasan */
> +
> +} /* anon namespace */
> +
> +gimple_opt_pass *
> +make_pass_hwasan_O0 (gcc::context *ctxt)
> +{
> +  return new pass_hwasan_O0 (ctxt);
> +}

Is it worth creating separate passes for this?  Seems like we could
just fork the implementation of the asan ones based on whether we're
using hwasan or not.

> diff --git a/gcc/gimplify.c b/gcc/gimplify.c
> index 15dfee903ab298f6a02e45d1affcc2260f3c911d..24ebedd490aea4ad634a92aa5742a83b1b0c0bb7 100644
> --- a/gcc/gimplify.c
> +++ b/gcc/gimplify.c
> @@ -1234,8 +1234,11 @@ asan_poison_variable (tree decl, bool poison, gimple_stmt_iterator *it,
>  
>    /* It's necessary to have all stack variables aligned to ASAN granularity
>       bytes.  */
> -  if (DECL_ALIGN_UNIT (decl) <= ASAN_SHADOW_GRANULARITY)
> -    SET_DECL_ALIGN (decl, BITS_PER_UNIT * ASAN_SHADOW_GRANULARITY);
> +  gcc_assert (!hwasan_sanitize_p () || hwasan_sanitize_stack_p ());
> +  unsigned shadow_granularity
> +    = hwasan_sanitize_p () ? HWASAN_TAG_GRANULE_SIZE : ASAN_SHADOW_GRANULARITY;
> +  if (DECL_ALIGN_UNIT (decl) <= shadow_granularity)
> +    SET_DECL_ALIGN (decl, BITS_PER_UNIT * shadow_granularity);
>  
>    HOST_WIDE_INT flags = poison ? ASAN_MARK_POISON : ASAN_MARK_UNPOISON;
>  
> @@ -15023,7 +15026,7 @@ gimplify_function_tree (tree fndecl)
>       if necessary.  */
>    cfun->curr_properties |= PROP_gimple_lva;
>  
> -  if (asan_sanitize_use_after_scope () && sanitize_flags_p (SANITIZE_ADDRESS))
> +  if (asan_sanitize_use_after_scope ())
>      asan_poisoned_variables = new hash_set<tree> ();
>    bind = gimplify_body (fndecl, true);
>    if (asan_poisoned_variables)
> diff --git a/gcc/internal-fn.c b/gcc/internal-fn.c
> index 8efc77d986b927dc4a37e396e6c710ffeda663ff..3397fa355f373f3ead5a02a07838a467f08f03c4 100644
> --- a/gcc/internal-fn.c
> +++ b/gcc/internal-fn.c
> @@ -467,6 +467,105 @@ expand_UBSAN_OBJECT_SIZE (internal_fn, gcall *)
>  /* This should get expanded in the sanopt pass.  */
>  
>  static void
> +expand_HWASAN_CHECK (internal_fn, gcall *)
> +{
> +  gcc_unreachable ();
> +}
> +
> +/* For hwasan stack tagging:
> +     Clear tags on the dynamically allocated space.
> +     For use after an object dynamically allocated on the stack goes out of
> +     scope.  */
> +static void
> +expand_HWASAN_ALLOCA_UNPOISON (internal_fn, gcall *gc)
> +{
> +  tree restored_position = gimple_call_arg (gc, 0);
> +  rtx restored_rtx = expand_expr (restored_position, NULL_RTX, VOIDmode,
> +				  EXPAND_NORMAL);
> +  rtx func = init_one_libfunc ("__hwasan_tag_memory");
> +  rtx off = expand_simple_binop (ptr_mode, MINUS, restored_rtx,
> +				 stack_pointer_rtx, NULL_RTX, 0,
> +				 OPTAB_LIB_WIDEN);
> +  rtx dynamic = convert_memory_address (ptr_mode, virtual_stack_dynamic_rtx);
> +  emit_library_call_value (func, NULL_RTX, LCT_NORMAL, VOIDmode,
> +			   dynamic, ptr_mode,
> +			   const0_rtx, QImode,
> +			   off, ptr_mode);
> +}
> +
> +/* For hwasan stack tagging:
> +     Return a tag to be used for a dynamic allocation.  */
> +static void
> +expand_HWASAN_CHOOSE_TAG (internal_fn, gcall *gc)
> +{
> +  tree tag = gimple_call_lhs (gc);
> +  rtx target = expand_expr (tag, NULL_RTX, VOIDmode, EXPAND_NORMAL);
> +  machine_mode mode = GET_MODE (target);
> +  gcc_assert (mode == QImode);
> +
> +  rtx base_tag = hwasan_extract_tag (hwasan_base ());
> +  gcc_assert (base_tag);
> +  rtx tag_offset = GEN_INT (hwasan_current_tag ());
> +  rtx chosen_tag = expand_simple_binop (QImode, PLUS, base_tag, tag_offset,
> +					target, /* unsignedp = */1,
> +					OPTAB_WIDEN);
> +
> +  gcc_assert (chosen_tag);
> +  /* Really need to put the tag into the `target` RTX.  */
> +  if (chosen_tag != target)
> +    {
> +      rtx temp = chosen_tag;
> +      machine_mode ret_mode = GET_MODE (chosen_tag);
> +      if (ret_mode != mode)
> +	temp = simplify_gen_unary (TRUNCATE, mode, chosen_tag, ret_mode);

Is this truncation necessary?  I'd have expected:

  expand_simple_binop (QImode, …)

to always return a QImode value, even for the widening path.

> +/* For hwasan stack tagging:
> +     Tag a region of space in the shadow stack according to the base pointer of
> +     an object on the stack.  */
> +static void
> +expand_HWASAN_MARK (internal_fn, gcall *gc)
> +{
> +  HOST_WIDE_INT flag = tree_to_shwi (gimple_call_arg (gc, 0));
> +  bool is_poison = ((asan_mark_flags)flag) == ASAN_MARK_POISON;
> +
> +  tree base = gimple_call_arg (gc, 1);
> +  gcc_checking_assert (TREE_CODE (base) == ADDR_EXPR);
> +  /* base is a pointer argument, hence in ptr_mode.
> +     We convert to Pmode for use in the hwasan_extract_tag and
> +     hwasan_create_untagged_base functions.
> +     We then convert the result to ptr_mode for the emit_library_call.  */

How does this ptr_mode<->Pmode stuff work for ILP32?  Wouldn't the tag
get lost by the Pmode->ptr_mode conversion?  Or does something ensure
that the tag is moved from bits 24+ to bits 56+ and back?

> +  rtx base_rtx = convert_memory_address (Pmode, expand_normal (base));
> +
> +  rtx tag = is_poison ? const0_rtx : hwasan_extract_tag (base_rtx);
> +  rtx address = hwasan_create_untagged_base (base_rtx);
> +  address = convert_memory_address (ptr_mode, address);
> +
> +  tree len = gimple_call_arg (gc, 2);
> +  gcc_assert (tree_fits_shwi_p (len));

Why's this guaranteed to be a SHWI?  For example, I couldn't see
anything that explicitly stopped calls to build_asan_poison_call_expr
with poly_int- or variable-sized decls.

Could we expand this to an aligned size in gimple, when generating
the HWASAN_MARK call?  E.g. we could reuse the alloca code for
computing an aligned size.

It'd probably be worth using the gimple_build routines in
gimple-fold.h to build the calculation, instead of calling
gimple_build_assign directly.  The operations will then get
folded on the fly.

> +  unsigned HOST_WIDE_INT size_in_bytes = tree_to_shwi (len);
> +  uint8_t tg_mask = HWASAN_TAG_GRANULE_SIZE - 1;
> +  gcc_assert (size_in_bytes);
> +  size_in_bytes = (size_in_bytes + tg_mask) & ~tg_mask;
> +  rtx size = gen_int_mode (size_in_bytes, ptr_mode);
> +
> +  rtx func = init_one_libfunc ("__hwasan_tag_memory");
> +  emit_library_call (func,
> +      LCT_NORMAL,
> +      VOIDmode,
> +      address, ptr_mode,
> +      tag, QImode,
> +      size, ptr_mode);

Formatting nit: should be indented under “func”.  Seems at least
LCT_NORMAL and VOIDmode could go on the same line as “func”.

Generally looks really good though (this and previous patches).

Thanks,
Richard


More information about the Gcc-patches mailing list