This is the mail archive of the gcc-patches@gcc.gnu.org mailing list for the GCC project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

[PATCH 17/X] [libsanitizer] Add hwasan Exception handling


When tagging the stack we need to ensure that any stack frames are
untagged during unwinding of any sort.  If this is not done, then
functions called later which re-use the stack can observe tag mismatches
on accesses to memory they have not tagged but that has been tagged
previously.

This applies equally to C++ exceptions as it does to longjmp and normal
function return.
The approach that LLVM has taken to account for C++ exceptions, is to
add a new personality routine on every function.  This new personality
routine calls the original personality routine, then untags the stack of
that function.

In order to untag the stack of the function currently being unwound, the
new personality wrapper needs to know the start and end of the current
stack frame.  The current implementation in libhwasan uses the frame
pointer to find the start of the stack frame.
https://github.com/llvm-mirror/compiler-rt/blob/69445f095c22aac2388f939bedebf224a6efcdaf/lib/hwasan/hwasan_exceptions.cpp#L58
This does not work for GCC, where the frame pointer is usually the same
as the stack pointer.

This patch demonstrates how adding C++ exception handling into GCC might
work, but currently does not work.  I intend to work on getting this
functionality, but do not consider it a blocker.

The current implementation ensures every function has a wrapped
personality function by modifying `get_personality_function` to check
for if we're handling exceptions and sanitizing the code with hwasan.
If so it returns a specially generated personality function that calls
`__hwasan_personality_wrapper` in libhwasan.


I've been testing the compiler instrumentation with a hack in libhwasan shown
below and things are mostly working, but I don't feel it's a good way forward.

I was wondering -- does anyone have any better ideas to keep this personality
method working for gcc?
I've also included the people that worked on exception handling in LLVM.  I
figure you may have tried other things before and hence have a good idea of
the pitfalls in this area.

My main aim is to get software tagging working for the kernel, so to me
exception handling is not a deal-breaker, but I would really like to get this
feature working.


Patch to libhwasan that I've been testing the instrumentation with.
######
diff --git a/libsanitizer/hwasan/hwasan_exceptions.cpp b/libsanitizer/hwasan/hwasan_exceptions.cpp
index 169e787..faec2e8 100644
--- a/libsanitizer/hwasan/hwasan_exceptions.cpp
+++ b/libsanitizer/hwasan/hwasan_exceptions.cpp
@@ -52,14 +52,16 @@ __hwasan_personality_wrapper(int version, _Unwind_Action actions,
   // Here we assume that the frame record appears after any locals. This is not
   // required by AAPCS but is a requirement for HWASAN instrumented functions.
   if ((actions & _UA_CLEANUP_PHASE) && rc == _URC_CONTINUE_UNWIND) {
+    uptr sp = get_cfa(context);
 #if defined(__x86_64__)
     uptr fp = get_gr(context, 6); // rbp
 #elif defined(__aarch64__)
-    uptr fp = get_gr(context, 29); // x29
+    uptr fp = *(uptr *)sp;
+    if (fp == 0)
+      return rc;
 #else
 #error Unsupported architecture
 #endif
-    uptr sp = get_cfa(context);
     TagMemory(sp, fp - sp, 0);
   }
######



gcc/ChangeLog:

2019-11-05  Matthew Malcomson  <matthew.malcomson@arm.com>

	* asan.c (hwasan_create_personality_thunk): New.
	* asan.h (hwasan_create_personality_thunk): New.
	* expr.c (get_personality_function): Add special function if
	using hwasan.



###############     Attachment also inlined for ease of reply    ###############


diff --git a/gcc/asan.h b/gcc/asan.h
index c27d679c117a9cc7b15b7b4c6710cf0b46050089..ff6adf2391ee1602a3c15755312a04f82d6369ce 100644
--- a/gcc/asan.h
+++ b/gcc/asan.h
@@ -36,6 +36,7 @@ extern void hwasan_emit_prologue (rtx *, rtx *, poly_int64 *, uint8_t *, size_t)
 extern rtx_insn *hwasan_emit_uncolour_frame (rtx, rtx, rtx_insn *);
 extern bool hwasan_expand_check_ifn (gimple_stmt_iterator *, bool);
 extern bool hwasan_expand_mark_ifn (gimple_stmt_iterator *);
+extern tree hwasan_create_personality_thunk (tree);
 extern bool memory_tagging_p (void);
 extern bool hwasan_sanitize_stack_p (void);
 extern bool gate_hwasan (void);
diff --git a/gcc/asan.c b/gcc/asan.c
index edfbf2048b67d3dc7be78a8e9961152c4fb44902..ef7c90e3358c8fa880b8e4002996f27541c26953 100644
--- a/gcc/asan.c
+++ b/gcc/asan.c
@@ -260,6 +260,10 @@ hash_set <tree> *asan_used_labels = NULL;
 
 static uint8_t tag_offset = 0;
 static rtx hwasan_base_ptr = NULL_RTX;
+static hash_map <uintptr_t, uintptr_t, simple_hashmap_traits<int_hash <uintptr_t, ~0ULL>, uintptr_t> >
+		*hwasan_wrapped_personalities = NULL;
+static tree hwasan_gr_decl = NULL_TREE;
+static tree hwasan_cfa_decl = NULL_TREE;
 
 /* Sets shadow offset to value in string VAL.  */
 
@@ -3951,6 +3955,220 @@ hwasan_tag_init ()
   tag_offset = HWASAN_STACK_BACKGROUND + 1;
 }
 
+tree
+hwasan_create_personality_thunk (tree orig_personality_decl)
+{
+  /* Only works with DWARF2 debugging.  */
+  /* Create a function called __hwasan_personality_thunk<orig_personality_name>.
+     (should be bare __hwasan_personality_thunk if function has no
+     personality function already).
+     That function should call __hwasan_personality_wrapper with arguments
+     - Same first 5 arguments.
+     - Original personality function argument.
+     - Unwind_GetGR function
+     - Unwind_GetCFA function
+     And then return.
+     TODO the personality_wrapper should be a tail-call.  */
+
+  if (! sanitize_flags_p (SANITIZE_HWADDRESS, orig_personality_decl))
+    return orig_personality_decl;
+  if (! hwasan_wrapped_personalities)
+    hwasan_wrapped_personalities = new hash_map<uintptr_t, uintptr_t, simple_hashmap_traits<int_hash <uintptr_t, ~0ULL>, uintptr_t> > (16);
+
+  tree *personality_wrapper
+    = (tree *)hwasan_wrapped_personalities->get
+		    ((uintptr_t)orig_personality_decl);
+  if (personality_wrapper)
+    return *personality_wrapper;
+
+
+  const char *name;
+  if (! orig_personality_decl)
+    name = "__hwasan_personality_thunk";
+  else
+    name = ACONCAT (("__hwasan_personality_thunk.",
+		     IDENTIFIER_POINTER (DECL_NAME (orig_personality_decl)),
+		     NULL));
+
+  /* Create a new function.  */
+  tree thunk_type = build_function_type_list (unsigned_type_node,
+					      integer_type_node,
+					      integer_type_node,
+					      long_long_unsigned_type_node,
+					      ptr_type_node, ptr_type_node,
+					      NULL_TREE);
+  tree thunk_decl = build_decl (UNKNOWN_LOCATION, FUNCTION_DECL,
+				get_identifier (name), thunk_type);
+  DECL_ARTIFICIAL (thunk_decl) = 1;
+  /* External linkage ??? (NO) */
+  DECL_EXTERNAL (thunk_decl) = 0;
+  /* Visible outside this translation unit??? */
+  TREE_PUBLIC (thunk_decl) = 0;
+  TREE_USED (thunk_decl) = 1;
+  DECL_IGNORED_P (thunk_decl) = 1;
+  DECL_INITIAL (thunk_decl) = make_node (BLOCK);
+  BLOCK_SUPERCONTEXT (DECL_INITIAL (thunk_decl)) = thunk_decl;
+  TREE_USED (DECL_INITIAL (thunk_decl)) = 1;
+  DECL_PRESERVE_P (thunk_decl) = 1;
+  DECL_UNINLINABLE (thunk_decl) = 1;
+  /* Having the `no_sanitize` attribute is partly to ensure the internal
+     function doesn't have a personality attributed to it.  */
+  DECL_ATTRIBUTES (thunk_decl)
+    = tree_cons (get_identifier ("no_sanitize"),
+		 build_int_cst (unsigned_type_node, SANITIZE_HWADDRESS),
+		 DECL_ATTRIBUTES (thunk_decl));
+  /* For arguments:
+	- Create a chain of ..._DECL nodes.
+	- Use DECL_ARGUMENTS (thunk_decl) = that_chain  */
+  tree arg[5] = {0};
+  const char* names[5] = {
+      "version",
+      "actions",
+      "exception_class",
+      "unwind_exception",
+      "context"
+  };
+  tree thunk_arg_types = TYPE_ARG_TYPES (thunk_type);
+  for (size_t i = 0; i < 5; i++)
+    {
+      tree type = TREE_VALUE (thunk_arg_types);
+      thunk_arg_types = TREE_CHAIN (thunk_arg_types);
+      arg[i] = build_decl (UNKNOWN_LOCATION, PARM_DECL,
+			   get_identifier (names[i]),
+			   type);
+      DECL_ARG_TYPE (arg[i]) = type;
+      DECL_CONTEXT (arg[i]) = thunk_decl;
+      DECL_ARTIFICIAL (arg[i]) = 1;
+      TREE_READONLY (arg[i]) = 0;
+      TREE_USED (arg[i]) = 0;
+    }
+
+  tree thunk_decl_chain = NULL_TREE;
+  for (int j = 4; j >= 0; j--)
+    {
+      DECL_CHAIN (arg[j]) = thunk_decl_chain;
+      thunk_decl_chain = arg[j];
+    }
+  DECL_ARGUMENTS (thunk_decl) = thunk_decl_chain;
+
+  tree resdecl
+    = build_decl (UNKNOWN_LOCATION, RESULT_DECL, NULL_TREE, unsigned_type_node);
+  DECL_RESULT (thunk_decl) = resdecl;
+  DECL_CONTEXT (resdecl) = thunk_decl;
+
+
+  /* Declaration for the wrapper we want to call (this function is provided in
+     libhwasan).  */
+  tree wrap_type = build_function_type_list (unsigned_type_node,
+					     integer_type_node,
+					     integer_type_node,
+					     long_long_unsigned_type_node,
+					     ptr_type_node, ptr_type_node,
+					     ptr_type_node, ptr_type_node,
+					     ptr_type_node, NULL_TREE);
+  tree wrap_decl = build_decl (UNKNOWN_LOCATION,
+			       FUNCTION_DECL,
+			       get_identifier ("__hwasan_personality_wrapper"),
+			       wrap_type);
+  DECL_ARTIFICIAL (wrap_decl) = 1;
+  DECL_EXTERNAL (wrap_decl) = 1;
+  TREE_PUBLIC (wrap_decl) = 1;
+
+  /* Create declarations for the functions we need to provide
+     __hwasan_personality_wrapper.  */
+  tree gr_decl;
+  if (hwasan_gr_decl)
+    gr_decl = hwasan_gr_decl;
+  else
+    {
+      gr_decl = build_decl (UNKNOWN_LOCATION,
+			    FUNCTION_DECL,
+			    get_identifier ("_Unwind_GetGR"),
+			    build_function_type_list (unsigned_type_node,
+						      ptr_type_node,
+						      integer_type_node,
+						      NULL_TREE));
+      DECL_ARTIFICIAL (gr_decl) = 1;
+      DECL_EXTERNAL (gr_decl) = 1;
+      TREE_PUBLIC (gr_decl) = 0;
+      hwasan_gr_decl = gr_decl;
+    }
+
+  tree cfa_decl;
+  if (hwasan_cfa_decl)
+    cfa_decl = hwasan_cfa_decl;
+  else
+    {
+      cfa_decl = build_decl (UNKNOWN_LOCATION,
+				  FUNCTION_DECL,
+				  get_identifier ("_Unwind_GetCFA"),
+				  build_function_type_list (unsigned_type_node,
+							    ptr_type_node,
+							    NULL_TREE));
+      DECL_ARTIFICIAL (cfa_decl) = 1;
+      DECL_EXTERNAL (cfa_decl) = 1;
+      TREE_PUBLIC (cfa_decl) = 0;
+      hwasan_cfa_decl = cfa_decl;
+    }
+
+  /* The below code partly taken from `cgraph_build_static_ctor_1`.
+     The aim is to create a new function using the gimple statements `body`
+     created above.  */
+  tree saved_current_decl = current_function_decl;
+  struct function *old_cfun = cfun;
+
+  current_function_decl = thunk_decl;
+  /* Sets `cfun` to the newly allocated function structure.  */
+  allocate_struct_function (thunk_decl, false);
+
+  /* Call the personality wrapper with the personality arguments plus pointers
+     to the relevant unwinder functions.
+     Put that call into the `body` tree that we will assign to the `thunk_decl`
+     function.  */
+  tree body = NULL_TREE;
+  tree personality_addr;
+  if (orig_personality_decl)
+    personality_addr = build_fold_addr_expr (orig_personality_decl);
+  else
+    {
+      tree ptr_type = build_pointer_type (TREE_TYPE (thunk_decl));
+      personality_addr = build_int_cst (ptr_type, 0);
+    }
+  tree call_expr = build_call_expr (wrap_decl, 8,
+				    /* 5 arguments from personality.  */
+				    arg[0], arg[1], arg[2],
+				    arg[3], arg[4],
+				    /* 3 arguments from compilers
+				       knowledge.  */
+				    personality_addr,
+				    build_fold_addr_expr (gr_decl),
+				    build_fold_addr_expr (cfa_decl));
+  tree ret_assign = build2 (INIT_EXPR, TREE_TYPE (resdecl), resdecl, call_expr);
+  tree return_statement = build1 (RETURN_EXPR, void_type_node, ret_assign);
+  append_to_statement_list (return_statement, &body);
+  DECL_SAVED_TREE (thunk_decl) = body;
+
+  gimplify_function_tree (thunk_decl);
+
+  opt_pass *saved_current_pass = current_pass;
+  cgraph_node::add_new_function (thunk_decl, false);
+  /* We save and restore `current_pass` because the value gets set to NULL
+     somewhere in `add_new_function` and hence we get a segmentation fault
+     later on.
+
+     TODO There"s probably a better way to do this, since I don't see other
+     functions using `cgraph_node::add_new_function` doing this save and
+     restore.  */
+  current_pass = saved_current_pass;
+  set_cfun (old_cfun);
+  current_function_decl = saved_current_decl;
+
+  gcc_assert (! hwasan_wrapped_personalities->put
+		    ((uintptr_t)orig_personality_decl,
+		     (uintptr_t)thunk_decl));
+  return thunk_decl;
+}
+
 rtx
 hwasan_extract_tag (rtx tagged_pointer)
 {
diff --git a/gcc/expr.c b/gcc/expr.c
index 2cd4bcb74cf92ccd7bd88ccb556b7e1eed2c6e06..46e3a75c6c6d66fd4f550fbd5b9f9c40837d2982 100644
--- a/gcc/expr.c
+++ b/gcc/expr.c
@@ -62,6 +62,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "ccmp.h"
 #include "gimple-fold.h"
 #include "rtx-vector-builder.h"
+#include "asan.h"
 
 
 /* If this is nonzero, we do not bother generating VOLATILE
@@ -12558,10 +12559,35 @@ get_personality_function (tree decl)
   tree personality = DECL_FUNCTION_PERSONALITY (decl);
   enum eh_personality_kind pk;
 
-  pk = function_needs_eh_personality (DECL_STRUCT_FUNCTION (decl));
+  /* Need to have a personality function on pretty much every function that can
+     allocate stack space for HWASAN.  This is because HWASAN needs to clear
+     the shadow stack of every function frame an exception goes through -- no
+     matter whether there is a cleanup to run or not.
+     Hence, if we're compiling with exceptions, and we're sanitizing this
+     function (because if we're not sanitizing this function the frame doesn't
+     have any coloured shadow stack) then we ensure we add a personality
+     function to it.  */
+  if (flag_exceptions && sanitize_flags_p (SANITIZE_HWADDRESS, decl))
+    pk = eh_personality_any;
+  else
+    pk = function_needs_eh_personality (DECL_STRUCT_FUNCTION (decl));
+
   if (pk == eh_personality_none)
     return NULL;
 
+  /* Want to add a personality routine to every function *except* those that
+     are specially marked.  */
+  if (sanitize_flags_p (SANITIZE_HWADDRESS, decl))
+    {
+      /* Set the personality for this function to be a wrapper around whatever
+	 the current personality is.  If this function has no associated
+	 personality then add a personality function to only clear shadow
+	 stack.  */
+      tree t = DECL_FUNCTION_PERSONALITY (decl);
+      personality = hwasan_create_personality_thunk (t);
+      DECL_FUNCTION_PERSONALITY (decl) = personality;
+    }
+
   if (!personality
       && pk == eh_personality_any)
     personality = lang_hooks.eh_personality ();

Attachment: hwasan-patch17.patch
Description: hwasan-patch17.patch


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]