diff mbox

Extend -fstack-protector-strong to cover calls with return slot

Message ID 52CBF834.3040004@redhat.com
State New
Headers show

Commit Message

Florian Weimer Jan. 7, 2014, 12:51 p.m. UTC
On 01/03/2014 10:43 PM, Florian Weimer wrote:

>> Lastly, I wonder if gimple_call_return_slot_opt_p is really what you are
>> after, why does NRV matter here?
>
> The C code we generate does not construct the returned value in place
> (presumably because the partial write would be visible with threads,
> longjmp etc.), unlike the C++ code.
>
> That's why I'm interested in instrumenting NRV-able calls only.  But
> gimple_call_return_slot_opt_p doesn't actually give me that.  The GIMPLE
> from the C test case looks like this (before and after applying your
> proposal):

I thought about this some more and I think it makes sense to add the 
instrumentation each time the return slot is used, both for C and C++. 
We don't if the called function is implemented in C or C++, so 
language-specific instrumentation is not entirely accurate.

I'm attaching a second version of the patch, splitting out the decl and 
bb analysis and using is_gimple_call.  Bootstrapped and 
regression-tested on x86_64-redhat-linux-gnu.

Comments

Jakub Jelinek Jan. 7, 2014, 1:07 p.m. UTC | #1
On Tue, Jan 07, 2014 at 01:51:00PM +0100, Florian Weimer wrote:
> +static bool
> +stack_protect_return_slot_p ()
> +{
> +  basic_block bb;
> +  
> +  FOR_ALL_BB_FN (bb, cfun)
> +    for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
> +	 !gsi_end_p (gsi); gsi_next (&gsi))
> +      {
> +	gimple stmt = gsi_stmt (gsi);
> +	if (is_gimple_call (stmt) && gimple_call_return_slot_opt_p (stmt))

I have to repeat, this is not the right test, it really is just an
optimization hint, nothing else.  Just look where it is set (unless it is
C++ where some NRV is performed in the FE as mandated by the languages)
- in pass_return_slot, which is an optimization pass, not run with -O0 or
-Og at all.  So, you'd stack protect something only if not -O0 or -Og, that
is just wrong, and only if it was suitable for NRV.  You really want to
protect all functions that call something that is returned by hidden
reference.

	Jakub
Florian Weimer Jan. 7, 2014, 1:27 p.m. UTC | #2
On 01/07/2014 02:07 PM, Jakub Jelinek wrote:
> On Tue, Jan 07, 2014 at 01:51:00PM +0100, Florian Weimer wrote:
>> +static bool
>> +stack_protect_return_slot_p ()
>> +{
>> +  basic_block bb;
>> +
>> +  FOR_ALL_BB_FN (bb, cfun)
>> +    for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
>> +	 !gsi_end_p (gsi); gsi_next (&gsi))
>> +      {
>> +	gimple stmt = gsi_stmt (gsi);
>> +	if (is_gimple_call (stmt) && gimple_call_return_slot_opt_p (stmt))
>
> I have to repeat, this is not the right test, it really is just an
> optimization hint, nothing else.  Just look where it is set (unless it is
> C++ where some NRV is performed in the FE as mandated by the languages)
> - in pass_return_slot, which is an optimization pass, not run with -O0 or
> -Og at all.

I don't understand.  The corresponding tree flag is set by 
gimplify_modify_expr_rhs, in the CALL_EXPR case:

	      if (use_target)
		{
		  CALL_EXPR_RETURN_SLOT_OPT (*from_p) = 1;
		  mark_addressable (*to_p);
		}

Isn't this an integral part of the gimplifier which also runs at -O0?  I 
certainly see the flag in the generated gimple at -O0, with the amended 
C test case:

foo11 ()
{
   int D.2037;
   struct B D.2038;

   D.2038 = global3 (); [return slot optimization]
   D.2037 = D.2038.a1;
   return D.2037;
}

As I said, I think the function gimple_call_return_slot_opt_p is 
misnamed because it's not actually an optimization.
Jakub Jelinek Jan. 7, 2014, 1:37 p.m. UTC | #3
On Tue, Jan 07, 2014 at 02:27:04PM +0100, Florian Weimer wrote:
> gimplify_modify_expr_rhs, in the CALL_EXPR case:
> 
> 	      if (use_target)
> 		{
> 		  CALL_EXPR_RETURN_SLOT_OPT (*from_p) = 1;
> 		  mark_addressable (*to_p);
> 		}

Yeah, that sets it in some cases too, not in other testcases.

Just look at how the flag is used when actually expanding it:

        if (target && MEM_P (target) && CALL_EXPR_RETURN_SLOT_OPT (exp))
          structure_value_addr = XEXP (target, 0);
        else
          {
            /* For variable-sized objects, we must be called with a target
               specified.  If we were to allocate space on the stack here,
               we would have no way of knowing when to free it.  */
            rtx d = assign_temp (rettype, 1, 1);
            structure_value_addr = XEXP (d, 0);
            target = 0;
          }

so, if it is set, the address of the var on the LHS is passed to the
function as hidden argument, if it is not set, we pass address of
a stack temporary instead.  Both the automatic var and the stack temporary
can overflow, if the callee does something wrong.

	Jakub
diff mbox

Patch

gcc/

2014-01-07  Florian Weimer  <fweimer@redhat.com>

	* cfgexpand.c (stack_protect_decl_p): New function, extracted from
	expand_used_vars.
	(stack_protect_return_slot_p): New function.
	(expand_used_vars): Call stack_protect_decl_p and
	stack_protect_return_slot_p for -fstack-protector-strong.

gcc/testsuite/

2014-01-07  Florian Weimer  <fweimer@redhat.com>

	* gcc.dg/fstack-protector-strong.c: Add coverage for return slots.
	* g++.dg/fstack-protector-strong.C: Likewise.

Index: gcc/cfgexpand.c
===================================================================
--- gcc/cfgexpand.c	(revision 206311)
+++ gcc/cfgexpand.c	(working copy)
@@ -1599,6 +1599,47 @@ 
   return 0;
 }
 
+/* Check if the current function has local referenced variables that
+   have their addresses taken, contain an array, or are arrays.  */
+
+static bool
+stack_protect_decl_p ()
+{
+  unsigned i;
+  tree var;
+
+  FOR_EACH_LOCAL_DECL (cfun, i, var)
+    if (!is_global_var (var))
+      {
+	tree var_type = TREE_TYPE (var);
+	if (TREE_CODE (var) == VAR_DECL
+	    && (TREE_CODE (var_type) == ARRAY_TYPE
+		|| TREE_ADDRESSABLE (var)
+		|| (RECORD_OR_UNION_TYPE_P (var_type)
+		    && record_or_union_type_has_array_p (var_type))))
+	  return true;
+      }
+  return false;
+}
+
+/* Check if the current function has calls that use a return slot.  */
+
+static bool
+stack_protect_return_slot_p ()
+{
+  basic_block bb;
+  
+  FOR_ALL_BB_FN (bb, cfun)
+    for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
+	 !gsi_end_p (gsi); gsi_next (&gsi))
+      {
+	gimple stmt = gsi_stmt (gsi);
+	if (is_gimple_call (stmt) && gimple_call_return_slot_opt_p (stmt))
+	  return true;
+      }
+  return false;
+}
+
 /* Expand all variables used in the function.  */
 
 static rtx
@@ -1669,22 +1710,8 @@ 
   pointer_map_destroy (ssa_name_decls);
 
   if (flag_stack_protect == SPCT_FLAG_STRONG)
-    FOR_EACH_LOCAL_DECL (cfun, i, var)
-      if (!is_global_var (var))
-	{
-	  tree var_type = TREE_TYPE (var);
-	  /* Examine local referenced variables that have their addresses taken,
-	     contain an array, or are arrays.  */
-	  if (TREE_CODE (var) == VAR_DECL
-	      && (TREE_CODE (var_type) == ARRAY_TYPE
-		  || TREE_ADDRESSABLE (var)
-		  || (RECORD_OR_UNION_TYPE_P (var_type)
-		      && record_or_union_type_has_array_p (var_type))))
-	    {
-	      gen_stack_protect_signal = true;
-	      break;
-	    }
-	}
+      gen_stack_protect_signal
+	= stack_protect_decl_p () || stack_protect_return_slot_p ();
 
   /* At this point all variables on the local_decls with TREE_USED
      set are not associated with any block scope.  Lay them out.  */
Index: gcc/testsuite/g++.dg/fstack-protector-strong.C
===================================================================
--- gcc/testsuite/g++.dg/fstack-protector-strong.C	(revision 206311)
+++ gcc/testsuite/g++.dg/fstack-protector-strong.C	(working copy)
@@ -32,4 +32,39 @@ 
   return global_func (a);
 }
 
-/* { dg-final { scan-assembler-times "stack_chk_fail" 2 } } */
+/* Frame addressed exposed through return slot. */
+
+struct B
+{
+  /* Discourage passing this struct in registers. */
+  int a1, a2, a3, a4, a5, a6, a7, a8, a9, a10;
+};
+
+B global_func ();
+void noop ();
+
+int foo3 ()
+{
+  return global_func ().a1;
+}
+
+int foo4 ()
+{
+  try {
+    noop ();
+    return 0;
+  } catch (...) {
+    return global_func ().a1;
+  }
+}
+
+int foo5 ()
+{
+  try {
+    return global_func ().a1;
+  } catch (...) {
+    return 0;
+  }
+}
+
+/* { dg-final { scan-assembler-times "stack_chk_fail" 5 } } */
Index: gcc/testsuite/gcc.dg/fstack-protector-strong.c
===================================================================
--- gcc/testsuite/gcc.dg/fstack-protector-strong.c	(revision 206311)
+++ gcc/testsuite/gcc.dg/fstack-protector-strong.c	(working copy)
@@ -131,4 +131,17 @@ 
   return bb.three;
 }
 
-/* { dg-final { scan-assembler-times "stack_chk_fail" 10 } } */
+struct B
+{
+  /* Discourage passing this struct in registers. */
+  int a1, a2, a3, a4, a5, a6, a7, a8, a9, a10;
+};
+
+struct B global3 (void);
+
+int foo11 ()
+{
+  return global3 ().a1;
+}
+
+/* { dg-final { scan-assembler-times "stack_chk_fail" 11 } } */