On 9/17/20 12:08 PM, Martin Sebor via Gcc-patches wrote:
On 9/16/20 9:23 PM, Jeff Law wrote:

On 9/15/20 1:47 PM, Martin Sebor wrote:
Overflowing the size of a dynamic allocation (e.g., malloc or VLA)
can lead to a subsequent buffer overflow corrupting the heap or
stack.  The attached patch diagnoses a subset of these cases where
the overflow/wraparound is still detectable.

Besides regtesting GCC on x86_64-linux I also verified the warning
doesn't introduce any false positives into Glibc or Binutils/GDB
builds on the same target.

Martin

gcc-96838.diff

PR middle-end/96838 - missing warning on integer overflow in calls to allocation functions

gcc/ChangeLog:

    PR middle-end/96838
    * calls.c (eval_size_vflow): New function.
    (get_size_range): Call it.  Add argument.
    (maybe_warn_alloc_args_overflow): Diagnose overflow/wraparound.
    * calls.h (get_size_range): Add argument.

gcc/testsuite/ChangeLog:

    PR middle-end/96838
    * gcc.dg/Walloc-size-larger-than-19.c: New test.
    * gcc.dg/Walloc-size-larger-than-20.c: New test.

If an attacker can control an integer overflow that feeds an allocation, then they can do all kinds of bad things.  In fact, when my son was asking me attack vectors, this is one I said I'd look at if I were a bad guy.


I'm a bit surprised you can't just query the range of the argument and get the overflow status out of that range, but I don't see that in the APIs.  How painful would it be to make that part of the API?  The conceptual model would be to just ask for the range of the argument to malloc which would include the range and a status bit indicating the computation might have overflowed.

 Do we know if it did/would have wrapped? sure.  since we have to do the math.    so you are correct in that the information is there. but is it useful?

We are in the very annoying habit of subtracting one by adding 0xFFFFFFF.  which means you get an overflow for unsigned when you subtract one.   From what I have seen of unsigned math, we would be flagging very many operations as overflows, so you would still have the difficulty of figuring out whether its a "real" overflow or a fake one because of the way we do unsigned math

At the very start, I did have an overflow flag in the range class...  but it was turning out to be fairly useless so it was removed.
.

I agree that being able to evaluate an expression in an as-if
infinite precision (in addition to its type) would be helpful.

SO again, we get back to adding 0x0fffff when we are trying to subtract one...  now, with infinite precision you are going to see

 [2,10]  - 1      we end up with [2,10]+0xFFFFFF, which will now give you  [0x100000001, 0x100000009]    so its going to look like it overflowed?



But just to make sure I understood correctly, let me ask again
using an example:

  void* f (size_t n)
  {
    if (n < PTRDIFF_MAX / 2)
      n = PTRDIFF_MAX / 2;

    return malloc (n * sizeof (int));
  }

Can the unsigned wraparound in the argument be readily detected?

On trunk, this ends up with the following:

  # RANGE [4611686018427387903, 18446744073709551615]
  _6 = MAX_EXPR <n_2(D), 4611686018427387903>;
  # RANGE [0, 18446744073709551615] NONZERO 18446744073709551612
  _1 = _6 * 4;
  ...
  p_5 = mallocD.1206 (_1); [tail call]
  ...
  return p_5;

so _1's range reflects the wraparound in size_t, but _6's range
has enough information to uncover it.  So detecting it is possible
and is done in the patch so we get a warning:

warning: argument 1 range [18446744073709551612, 0x3fffffffffffffffc] is too large to represent in ‘long unsigned int’ [-Walloc-size-larger-than=]
    6 |   return malloc (n * sizeof (int));
      |          ^~~~~~~~~~~~~~~~~~~~~~~~~

The code is very simplistic and only handles a small subset of cases.
It could be generalized and exposed by a more generic API but it does
seem like the ranger must already have all the logic built into it so
if it isn't exposed now it should be a matter of opening it up.

everything is exposed in range-ops.  well, mostly.
if we have _1 = _6 * 4

if one wanted to do that infinite precision, you query the range for _6, and the range for 4 (which would be [4,4] :-)
range_of_expr (op1r, _6, stmt)
range_of_expr (op2r, 4, stmt)

you could take their current types, and cast those ranges to whatever the next higher precsion is,
range_cast  (op1r, highertype)
range_cast (op2r, highertype)
then invoke the operation on those parameters

gimple_range_fold (r, stmt,  op1r, op2r)

and that will do your operation in the higher precision.  you could compare that to the value in regular precision too i suppose.

The ranger is designed to track ranges as they are represented in the IL.  You are asking for us to interpret the IL as something other than what is there, and increase the precision.  Thats a different task.

could that be done?    maybe. It might involve parallel tracking of ssa-Name ranges in a higher precision... and recalculating every expression using those values.   Im not convinced that a generalized ranger which works in higher precision is really going to solve the problem the way you hope it would.. i think we'd get a lot of false info when unsigned values are involved

If I were to see a clear solution/description as to what is a problematic overflow and what isnt, there might be something more general that could be done...

I mentioned it to Aldy in response to the irange best practices
document.  He says there's no way to do that and no plans to
make it possible.

Thats not quite what he said.   . He said  "If the operation may wrap or overflow, it will show up as so in the range.  I don't think we have any plans of changing this behavior."

In fact. looking back, he said the same thing I just did....  There IS a mechanism there for working in higher precision should one desire to do so, but as I pointed out above, Im not convinced as a general thing in the ranger it will work how you imagine.

My guess is what you really want is to be invoking range-ops on stmts you care about whether they might overflow or not with higher precision versions and then identify the cases which trigger that you actually care about.

So maybe you want an "overflow calculator" which is similar to the ranger, and  identifies which kinds of stmts expect certain range of results, and if they get results outside of  the expected ranges, triggers a flag. maybe it could convert all unsigned and signed  values to signed, and then work in a higher precision,  and then if any intermediate result cant be accurately cast back to the original type,  it gets flagged?  is that the kind of overflow that is cared about?

I dont know... this isnt my space :-)       Im just telling you that I'm not sure the current range query mechanism is the right place to be asking if an "overflow" occurred because I think the general answer wont be useful.. too many kinds of overflows thrown together.

I think this requires more specifoc detailed information, and maybe a simple overflow analyzer will do the trick based on range-ops.... I think range-ops has the required abilities.  whether it needs some enhancing, or something else exposed,  I'm not sure yet


Andrew

PS  One could always take a ranger and overload the range_of_stmt() routine that calculates results, so that it calls current functionality, then try converting the arguements to  higher precision ranges and re-invoking range-ops on those arguments, get a new higher precision range back and then look at the results vs the normal ones.  Might be a place to start experimenting...   then pass a flag along sayign an overflow occurred.
.



Reply via email to