diff mbox

[PR19351,C++] Fix heap overflow in operator new[]

Message ID 87ipwd6quz.fsf@mid.deneb.enyo.de
State New
Headers show

Commit Message

Florian Weimer Feb. 21, 2011, 9:05 p.m. UTC
* Florian Weimer:

> (I hope I'll have the patch with the option ready soon, perhaps
> tomorrow.)

Sorry, took much longer than expected.  The default is currently on.

I have run "make check-c++" with no new failures on x86_64-gnu-linux
twice, with the operator new[] check enabled and disabled; there were
no new failures.  If the check is disabled, trunk and patch produce
identical assembler code for the test case.

I still need some guidance where to put the test case.  There don't
seem to be any direct tests for operator new[] funcionality.

2011-02-21  Florian Weimer  <fw@deneb.enyo.de>

	* c.opt (foperator-new-overflow-check): New.

2011-02-21  Florian Weimer  <fw@deneb.enyo.de>

	PR c++/19351

	* init.c (maybe_build_size_mult_saturated, build_new_size_expr): Add.
	(build_new_1): Use these new functions in size calculations.

	* call.c (build_operator_new_call): Add size_with_cookie argument.
	* cp-tree.h (build_operator_new_call): Likewise.

Comments

Florian Weimer March 14, 2011, 9:25 p.m. UTC | #1
* Florian Weimer:

> I have run "make check-c++" with no new failures on x86_64-gnu-linux
> twice, with the operator new[] check enabled and disabled; there were
> no new failures.  If the check is disabled, trunk and patch produce
> identical assembler code for the test case.
>
> I still need some guidance where to put the test case.  There don't
> seem to be any direct tests for operator new[] funcionality.
>
> 2011-02-21  Florian Weimer  <fw@deneb.enyo.de>
>
> 	* c.opt (foperator-new-overflow-check): New.
>
> 2011-02-21  Florian Weimer  <fw@deneb.enyo.de>
>
> 	PR c++/19351
>
> 	* init.c (maybe_build_size_mult_saturated, build_new_size_expr): Add.
> 	(build_new_1): Use these new functions in size calculations.
>
> 	* call.c (build_operator_new_call): Add size_with_cookie argument.
> 	* cp-tree.h (build_operator_new_call): Likewise.

Sorry, but: ping?

<http://gcc.gnu.org/ml/gcc-patches/2011-02/msg01376.html>
Jason Merrill March 31, 2011, 9:12 p.m. UTC | #2
Sorry it's taken so long to review this.

On 02/21/2011 04:05 PM, Florian Weimer wrote:
>  build_operator_new_call (tree fnname, VEC(tree,gc) **args,
> -                        tree *size, tree *cookie_size,
> +                        tree *size, tree size_with_cookie, tree *cookie_size,

We don't need both size_with_cookie and cookie_size here.  Let's change 
the cookie_size parameter to be size_with_cookie instead (and adjust the 
places in build_new_1 that check for cookie_size being set or not).

> +   flag_new_overflow_check is true.
> + */

Close comment on the last line of text rather than the next line.

> +  if (!flag_new_overflow_check)
> +    return result;

Let's check for constant results here.  If we have a TREE_CONSTANT 
result that overflows, we can handle it even if we aren't emitting the 
checks for non-constant values.  And if we have a TREE_CONSTANT result 
that doesn't overflow, we don't have to bother building up the 
complicated trees in order to fold them away.

> +  return build3 (COND_EXPR, size_type_node,
> +                build2 (EQ_EXPR, size_type_node, mul2, size_zero_node),
> +                add == NULL_TREE ? size_zero_node : add,
> +                build3 (COND_EXPR, size_type_node,
> +                        build2 (LE_EXPR, size_type_node, mul1, max_mul1),
> +                        result, TYPE_MAX_VALUE (size_type_node)));

Go ahead and fold here.  The C++ front end has not yet been converted to 
delay folding like the C front end.

> +static tree
> +build_new_size_expr (tree elt_type, tree nelts, tree cookie_size)

This function needs a comment.

> +    nelts = maybe_build_size_mult_saturated
> +      (convert (sizetype, nelts),
> +       convert (sizetype, array_type_nelts_top (elt_type)),
> +       NULL_TREE);

It seems unfortunate to do this runtime checking more than once for 
multi-dimensional new.  Since the element size and all but one of the 
array dimensions are going to be constant in most cases, let's try to 
multiply the element size with the constant dimensions before we bring 
in the potentially non-constant dimension.

>     I still need some guidance where to put the test case.  There don't
>     seem to be any direct tests for operator new[] funcionality.

Let's put it in g++.dg/init.

Jason
Florian Weimer May 29, 2011, 7:02 p.m. UTC | #3
* Jason Merrill:

> Sorry it's taken so long to review this.

Same here. *sigh*  Thanks for your comments.

> On 02/21/2011 04:05 PM, Florian Weimer wrote:
>>  build_operator_new_call (tree fnname, VEC(tree,gc) **args,
>> -                        tree *size, tree *cookie_size,
>> +                        tree *size, tree size_with_cookie, tree *cookie_size,
>
> We don't need both size_with_cookie and cookie_size here.  Let's
> change the cookie_size parameter to be size_with_cookie instead (and
> adjust the places in build_new_1 that check for cookie_size being set
> or not).

Okay.

>> +  if (!flag_new_overflow_check)
>> +    return result;
>
> Let's check for constant results here.  If we have a TREE_CONSTANT
> result that overflows, we can handle it even if we aren't emitting the
> checks for non-constant values.

I assume I can report an error in this case?

> And if we have a TREE_CONSTANT result that doesn't overflow, we
> don't have to bother building up the complicated trees in order to
> fold them away.

Understood.  This is sort-of needed ...

>> +    nelts = maybe_build_size_mult_saturated
>> +      (convert (sizetype, nelts),
>> +       convert (sizetype, array_type_nelts_top (elt_type)),
>> +       NULL_TREE);
>
> It seems unfortunate to do this runtime checking more than once for
> multi-dimensional new.  Since the element size and all but one of the
> array dimensions are going to be constant in most cases, let's try to
> multiply the element size with the constant dimensions before we bring
> in the potentially non-constant dimension.

... to implement this anyway.  I plan to accumulate constant and
variable factors separately, and multiply them together in the end.
VARIABLE * VARIABLE will still incur an expensive overflow check
(involving integer division, even), but there's currently no way
around that.

I already have something in that direction, but it is still rather
messy.  Hopefully, it will be a temporary measure only, and this
burden can go away when the middle-end learns about saturating
arithmetic, or we implement std::bad_array_new_length from C++0X (and
have removed VLA support).

>>     I still need some guidance where to put the test case.  There don't
>>     seem to be any direct tests for operator new[] funcionality.
>
> Let's put it in g++.dg/init.

Okay.  Curiously, the test suite has rather poor coverage of operator
new[] right now.  Multi-dimensional new is also extremely rare in the
C++ code bases I looked at.
Richard Biener May 29, 2011, 7:51 p.m. UTC | #4
On Sun, May 29, 2011 at 9:02 PM, Florian Weimer <fw@deneb.enyo.de> wrote:
> * Jason Merrill:
>
>> Sorry it's taken so long to review this.
>
> Same here. *sigh*  Thanks for your comments.
>
>> On 02/21/2011 04:05 PM, Florian Weimer wrote:
>>>  build_operator_new_call (tree fnname, VEC(tree,gc) **args,
>>> -                        tree *size, tree *cookie_size,
>>> +                        tree *size, tree size_with_cookie, tree *cookie_size,
>>
>> We don't need both size_with_cookie and cookie_size here.  Let's
>> change the cookie_size parameter to be size_with_cookie instead (and
>> adjust the places in build_new_1 that check for cookie_size being set
>> or not).
>
> Okay.
>
>>> +  if (!flag_new_overflow_check)
>>> +    return result;
>>
>> Let's check for constant results here.  If we have a TREE_CONSTANT
>> result that overflows, we can handle it even if we aren't emitting the
>> checks for non-constant values.
>
> I assume I can report an error in this case?

Only a warning.  The code is only undefined at runtime.  But you
could convert the allocation to __builtin_trap () (hmm, what
about exceptions? Is such an allocation required to throw?)

Richard.

>> And if we have a TREE_CONSTANT result that doesn't overflow, we
>> don't have to bother building up the complicated trees in order to
>> fold them away.
>
> Understood.  This is sort-of needed ...
>
>>> +    nelts = maybe_build_size_mult_saturated
>>> +      (convert (sizetype, nelts),
>>> +       convert (sizetype, array_type_nelts_top (elt_type)),
>>> +       NULL_TREE);
>>
>> It seems unfortunate to do this runtime checking more than once for
>> multi-dimensional new.  Since the element size and all but one of the
>> array dimensions are going to be constant in most cases, let's try to
>> multiply the element size with the constant dimensions before we bring
>> in the potentially non-constant dimension.
>
> ... to implement this anyway.  I plan to accumulate constant and
> variable factors separately, and multiply them together in the end.
> VARIABLE * VARIABLE will still incur an expensive overflow check
> (involving integer division, even), but there's currently no way
> around that.
>
> I already have something in that direction, but it is still rather
> messy.  Hopefully, it will be a temporary measure only, and this
> burden can go away when the middle-end learns about saturating
> arithmetic, or we implement std::bad_array_new_length from C++0X (and
> have removed VLA support).
>
>>>     I still need some guidance where to put the test case.  There don't
>>>     seem to be any direct tests for operator new[] funcionality.
>>
>> Let's put it in g++.dg/init.
>
> Okay.  Curiously, the test suite has rather poor coverage of operator
> new[] right now.  Multi-dimensional new is also extremely rare in the
> C++ code bases I looked at.
>
Florian Weimer May 29, 2011, 8:05 p.m. UTC | #5
* Richard Guenther:

>>>> +  if (!flag_new_overflow_check)
>>>> +    return result;
>>>
>>> Let's check for constant results here.  If we have a TREE_CONSTANT
>>> result that overflows, we can handle it even if we aren't emitting the
>>> checks for non-constant values.
>>
>> I assume I can report an error in this case?
>
> Only a warning.  The code is only undefined at runtime.  But you
> could convert the allocation to __builtin_trap () (hmm, what
> about exceptions? Is such an allocation required to throw?)

With a std::nothrow specifier, I don't think it is.

But without that, it's required to throw std::bad_array_new_length in
C++0X.  C++03 is ambiguous.
diff mbox

Patch

diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt
index bb928fa..78e4020 100644
--- a/gcc/c-family/c.opt
+++ b/gcc/c-family/c.opt
@@ -898,6 +898,10 @@  foperator-names
 C++ ObjC++
 Recognize C++ keywords like \"compl\" and \"xor\"
 
+foperator-new-overflow-check
+C++ ObjC++ Var(flag_new_overflow_check) Init(1)
+Check for overflow during operator new[]
+
 foptional-diags
 C++ ObjC++ Ignore
 Does nothing.  Preserved for backward compatibility.
diff --git a/gcc/cp/call.c b/gcc/cp/call.c
index 8dccbbe..15bc570 100644
--- a/gcc/cp/call.c
+++ b/gcc/cp/call.c
@@ -3635,7 +3635,7 @@  build_new_function_call (tree fn, VEC(tree,gc) **args, bool koenig_p,
 
 tree
 build_operator_new_call (tree fnname, VEC(tree,gc) **args,
-			 tree *size, tree *cookie_size,
+			 tree *size, tree size_with_cookie, tree *cookie_size,
 			 tree *fn)
 {
   tree fns;
@@ -3706,7 +3706,7 @@  build_operator_new_call (tree fnname, VEC(tree,gc) **args,
        if (use_cookie)
 	 {
 	   /* Update the total size.  */
-	   *size = size_binop (PLUS_EXPR, *size, *cookie_size);
+	   *size = size_with_cookie;
 	   /* Update the argument list to reflect the adjusted size.  */
 	   VEC_replace (tree, *args, 0, *size);
 	 }
diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index 238d0cf..4598f10 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -4614,7 +4614,7 @@  extern tree build_user_type_conversion		(tree, tree, int);
 extern tree build_new_function_call		(tree, VEC(tree,gc) **, bool, 
 						 tsubst_flags_t);
 extern tree build_operator_new_call		(tree, VEC(tree,gc) **, tree *,
-						 tree *, tree *);
+						 tree, tree *, tree *);
 extern tree build_new_method_call		(tree, tree, VEC(tree,gc) **,
 						 tree, int, tree *,
 						 tsubst_flags_t);
diff --git a/gcc/cp/init.c b/gcc/cp/init.c
index e590118..87a90a4 100644
--- a/gcc/cp/init.c
+++ b/gcc/cp/init.c
@@ -1913,6 +1913,43 @@  diagnose_uninitialized_cst_or_ref_member (tree type, bool using_new, bool compla
   return diagnose_uninitialized_cst_or_ref_member_1 (type, type, using_new, complain);
 }
 
+/* Multiplies MUL1 with MUL2, and adds ADD.  Returns
+   std::limits<size_t>::max() if the result cannot be be represented
+   as a size_t value.  If ADD is null_tree, treat it as a zero
+   constant.  Uses saturated arithmetic only if
+   flag_new_overflow_check is true.
+ */
+static tree
+maybe_build_size_mult_saturated (tree mul1, tree mul2, tree add)
+{
+  tree max_mul1, result;
+  result = size_binop (MULT_EXPR, mul1, mul2);
+  if (add != NULL_TREE)
+    result = size_binop (PLUS_EXPR, result, add);
+  if (!flag_new_overflow_check)
+    return result;
+  max_mul1 = TYPE_MAX_VALUE (size_type_node);
+  if (add != NULL_TREE)
+    max_mul1 = build2 (MINUS_EXPR, size_type_node, max_mul1, add);
+  max_mul1 = build2 (TRUNC_DIV_EXPR, size_type_node, max_mul1, mul2);
+  return build3 (COND_EXPR, size_type_node,
+		 build2 (EQ_EXPR, size_type_node, mul2, size_zero_node),
+		 add == NULL_TREE ? size_zero_node : add,
+		 build3 (COND_EXPR, size_type_node,
+			 build2 (LE_EXPR, size_type_node, mul1, max_mul1),
+			 result, TYPE_MAX_VALUE (size_type_node)));
+}
+
+static tree
+build_new_size_expr (tree elt_type, tree nelts, tree cookie_size)
+{
+  tree elt_size = size_in_bytes (elt_type);
+  if (nelts == NULL_TREE)
+    return elt_size;
+  return maybe_build_size_mult_saturated
+    (convert (sizetype, nelts), elt_size, cookie_size);
+}
+
 /* Generate code for a new-expression, including calling the "operator
    new" function, initializing the object, and, if an exception occurs
    during construction, cleaning up.  The arguments are as for
@@ -1982,10 +2019,10 @@  build_new_1 (VEC(tree,gc) **placement, tree type, tree nelts,
   for (elt_type = type;
        TREE_CODE (elt_type) == ARRAY_TYPE;
        elt_type = TREE_TYPE (elt_type))
-    nelts = cp_build_binary_op (input_location,
-				MULT_EXPR, nelts,
-				array_type_nelts_top (elt_type),
-				complain);
+    nelts = maybe_build_size_mult_saturated
+      (convert (sizetype, nelts),
+       convert (sizetype, array_type_nelts_top (elt_type)),
+       NULL_TREE);
 
   if (TREE_CODE (elt_type) == VOID_TYPE)
     {
@@ -2037,10 +2074,6 @@  build_new_1 (VEC(tree,gc) **placement, tree type, tree nelts,
       return error_mark_node;
     }
 
-  size = size_in_bytes (elt_type);
-  if (array_p)
-    size = size_binop (MULT_EXPR, size, convert (sizetype, nelts));
-
   alloc_fn = NULL_TREE;
 
   /* If PLACEMENT is a single simple pointer type not passed by
@@ -2104,8 +2137,8 @@  build_new_1 (VEC(tree,gc) **placement, tree type, tree nelts,
 	  if (array_p && TYPE_VEC_NEW_USES_COOKIE (elt_type))
 	    {
 	      cookie_size = targetm.cxx.get_cookie_size (elt_type);
-	      size = size_binop (PLUS_EXPR, size, cookie_size);
 	    }
+	  size = build_new_size_expr (elt_type, nelts, cookie_size);
 	  /* Create the argument list.  */
 	  VEC_safe_insert (tree, gc, *placement, 0, size);
 	  /* Do name-lookup to find the appropriate operator.  */
@@ -2136,14 +2169,24 @@  build_new_1 (VEC(tree,gc) **placement, tree type, tree nelts,
 	{
 	  /* Use a global operator new.  */
 	  /* See if a cookie might be required.  */
+	  tree size_with_cookie;
+	  size = build_new_size_expr (elt_type, nelts, NULL_TREE);
 	  if (array_p && TYPE_VEC_NEW_USES_COOKIE (elt_type))
-	    cookie_size = targetm.cxx.get_cookie_size (elt_type);
+	    {
+	      cookie_size = targetm.cxx.get_cookie_size (elt_type);
+	      size_with_cookie = build_new_size_expr
+		(elt_type, nelts, cookie_size);
+	    }
 	  else
-	    cookie_size = NULL_TREE;
+	    {
+	      cookie_size = NULL_TREE;
+	      size_with_cookie = size;
+	    }
 
-	  alloc_call = build_operator_new_call (fnname, placement,
-						&size, &cookie_size,
-						&alloc_fn);
+	  alloc_call = build_operator_new_call
+	    (fnname, placement,
+	     &size, size_with_cookie, &cookie_size,
+	     &alloc_fn);
 	}
     }
 
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 5295c39..c066693 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -1998,6 +1998,18 @@  Do not treat the operator name keywords @code{and}, @code{bitand},
 @code{bitor}, @code{compl}, @code{not}, @code{or} and @code{xor} as
 synonyms as keywords.
 
+@item -foperator-new-overflow-check
+@itemx -fno-operator-new-overflow-check
+@opindex foperator-new-overflow-check
+@opindex fno-operator-new-overflow-check
+If @option{-foperator-new-overflow-check} is specified, @code{operator
+new[]} expressions will throw @code{std::bad_alloc} if the required size
+cannot be represented as a value of type @code{size_t}.  With
+@option{-fno-operator-new-overflow-check}, no such checks are performed
+and @code{operator new[]} may return a pointer to a memory area which
+is too small for the requested number of elements.  The default is to
+perform the check, which increases code size slightly.
+
 @item -fno-optional-diags
 @opindex fno-optional-diags
 Disable diagnostics that the standard says a compiler does not need to