
Solutions to Integer Overflow - deafcalculus
http://blog.regehr.org/archives/1401
======
legulere
> worse than C and C++’s UB-on-overflow which at least permits an
> implementation to trap.

That's only true for signed integers, overflow is defined as wrapping for
unsigned integers.

And even with signed integers the compilers handle it way worse than wrapping:
When LLVM finds a overflow that will always happen, it won't report it (Only
Clang will do that, but that's before any optimisations and thus can see far
less) and optimize it to a `undef` value. The backend subsequently will treat
the `undef` value like it's in all registers. If you make a function call with
an `undef` value you will pass what's in the register used according to the
calling convention before. This can be a pointer that will be leaking info
about ASLR or a register that can contain user controlled information.

As an example compile this with optimisations (should work the same with
signed integer overflow):

    
    
        #include <stdio.h>
        
        int main(int argc, char ** argv) {
          int a = 1;
          int b = 0;
          printf("%d", a/b);
        }
    

This prints some ASLR-protected pointer which you can use to calculate the
offset, which allows you to break ASLR. I haven't seen this attack mentioned
before and it's probably extremely unlikely to happen in the wild, but it's
possible.

I also found the article lacking because it doesn't talk about saturation
arithmetics:
[https://en.wikipedia.org/wiki/Saturation_arithmetic](https://en.wikipedia.org/wiki/Saturation_arithmetic)
Rust offers some functions for it and it can often be less problematic than
wrapping, for instance when dealing with the length of things.

~~~
nkurz
_As an example compile this with optimisations_

Could you be more specific? I'm not sure I'm following your example. Referring
to the assembly output here might help me:

[https://godbolt.org/g/RN1Ngm](https://godbolt.org/g/RN1Ngm)

~~~
legulere
Check the difference when you change `b` to 1. Then `a/b` is optimized to 1
and not undef. Thus there's an additional `movl $1, %esi`. This is lacking in
the example where `a/b` is undef. So `printf` gets passed whatever value is in
`%esi`. There is some code that calls main in the binary which is lacking in
your disassembly so I can't exactly tell you where `%esi` is from.

[https://godbolt.org/g/cEaAVR](https://godbolt.org/g/cEaAVR)

~~~
nkurz
OK, I think see what you are saying.

Your point is that if we can convince the compiler to optimize out the
initialization of a register, we can get printf() to "leak" the previous value
of that register. The example of %esi is specific to this variable being the
first to occur in format string and the fact that you are running on a non-
Windows 64-bit system, where the format string is passed in %rdi and the first
format variable in %rsi (and %ax is zeroed to specify that no vector arguments
are involved).

It happens that current Clang makes this and only this omission, although GCC
and ICC leave crash because they try to execute 'idiv' by zero. Confusingly,
this error is reported as a "floating point exception" even though no floating
point numbers are involved. If one wanted to see the uninitialized value for
these other compilers, you could probably just use a bare printf("%llx")
without any argument.

------
Animats
_Explicitly ranged integers as seen in Ada are also very nice, there’s no
reason that traps should only occur at the 32-bit or 64-bit boundaries._

Pascal had that. Somehow it got lost.

 _The formal verification community ignored integer overflow for far too long_

Well, we didn't, back in the early 1980s.[1] But I had that argument with
other people in the formal verification community.

I once wrote a paper called "Type Integer Considered Harmful". (Unfortunately,
I've lost this.) This was back in the era when there were mixed 16-bit and
32-bit integers as machines transitioned to 32 bit. My position was that it
was the compiler's job to size intermediate expressions so that overflow
couldn't occur there if it wasn't going to occur in the final result. Overflow
in the final result should be detected at run time or proved impossible.

Here's what that means. Consider

    
    
       uint_32 a,b,c;
       a = b + c;
    

Overflow of "b + c" implies overflow in a, so we just have to check for
overflow.

    
    
       uint_32 a,b,c,d;
       a = b + c - d; // ordered as (b + c) - d
       

Overflow of "b + c" does not imply overflow in a. So the "b + c" sum has to be
done in 64 bit.

    
    
       uint_32 a,b,c;
       a = b * c;
    

Again, overflow in "b + c" implies overflow in a, so we don't need a bigger
intermediate value.

    
    
       uint_32 a,b,c,d;
       a = (b * c) / d;
    

The "b * c" term needs to be 64-bit.

    
    
       uint_32 a,b,c,d,e;
       a = (b * c * d) / e;
    

Either we need 128 bit arithmetic, or refuse to compile this. Some expressions
you just can't do right with the machine hardware. The user could write

    
    
       uint_32 a,b,c,d,e;
       a = (uint_32(b * c) * d) / e;
    

which means trapping on overflow if "b * c" is bigger than an uint_32, and the
compiler must generate a 64-bit product for "uint_32(b * c) * d)".

Languages tend to view numeric type conversion as a named type problem, rather
than a numeric range problem. That leads to wrong answers in some cases. The
goal is to get the right answer or trap.

If you want unsigned arithmetic that wraps, you would write

    
    
       uint_32 a,b;
       a = (a + b) % (2^32);
    

The compiler should recognize this as an optimization and do 32-bit unsigned
wrapped arithmetic without trapping. The programmer says what they mean,
rather than relying on the semantics of the hardware, which might change.
(This used to be a bigger issue when we had to support 36 bit and 48 bit CPUs.
There are still some odd-size DSP chips.)

[1]
[http://www.animats.com/papers/verifier/verifiermanual.pdf](http://www.animats.com/papers/verifier/verifiermanual.pdf)

------
morecoffee
> The Rust developers have adopted a hybrid solution where integer overflows
> trap in debug builds and wrap in optimized builds.

This is not a solution. The production binaries and the test binaries run on
different data. We try as programmers and release engineers to try to get the
test environment to match production, but it's hard to do right, and takes
constant upkeep.

Trapping should be the default, and disabled only by special user intent,
which optimization flags are not.

~~~
dbaupp
Many (maybe most) languages are OK with changing behaviour/code between debug
and release builds, e.g. disabling `assert`s and even the optimisations
themselves. Of course, this is suboptimal in some cases, and thus providing a
way to disable it is good and thus rustc has -C debug-assertions=on, and a
currently-unstable flag to control just the overflow checks themselves.
Seguing from that first flag, Rust makes it explicit when an assertion is OK
to omit by providing both debug_assert! (removed in release) and assert!
(never removed), so I guess Rust is in a middle ground that's the reverse of
the middle ground of a language like, say, Swift, which removes assertions but
not overflow checks in its default optimised build.

~~~
SamReidHughes
Swift has release-mode assertions, they're done with precondition().

~~~
dbaupp
You are correct, but my point was implicitly that the default thing people
reach for is assert, especially since the name precondition sounds like it is
only ok to call at the start of a function.

------
SamReidHughes
Using bignums means that arithmetic operations would have to check their
arguments for whether they're bignums and also check the result for overflow,
and that's going to be a lot worse than some branch-on-OF, which I'd think any
sane CPU should statically predict not to be taken. The needs for things like
array indexes, sizes of things, and other stuff that isn't in-band user data,
these janitorial integer values, are such that you don't need (or want)
bignums for them.

If I ruled the universe, I would declare overflow-checked signed and unsigned
types to be the ordinary numeric types, and then in-band user values -- the
data the program's written to work with, like coordinates of stuff in video
games, values in spreadsheets, data in strings that is going through a crypto
algorithm -- are the sort of thing that would deviate from the ordinary. They
might use bignums, or floats or doubles or float128's, or unchecked 2's
complement integer types, depending on the needs of correctness and
performance.

Most of the operations on janitorial integers can have the overflow checks be
optimized away, anyway. On one 20k line C program I counted, and I forget the
exact number of arithmetic operations where (1) the overflow check would be
gratuitous and (2) where omitting the overflow check wasn't a trivial
optimization, was less than 10.

(Edited to make clear I was talking about gratuitous checks, by which I mean
the overflow check would provably never catch overflow, and where a human
programmer might feel bad about doing the wasteful check. There were less than
10 provably unnecessary overflow checks that were not "trivially" provable.
One example of an overflow check I would not consider gratuitous is a doubling
in capacity of a std::vector<T> when adding an element, or multiplying by a
sizeof before a malloc. It could be theoretically provable if you bound other
integers globally that such an overflow could never happen, but a human
editing the code would be OK with the check and its performance implications.)

~~~
junke
That's the case in practice (both the internal limits and the optimizations).
For example Common Lisp's ARRAY-TOTAL-SIZE-LIMIT is a fixnum. The default
numeric type is not bignum, however, the numeric tower is here to use them
only when needed.

~~~
SamReidHughes
That distinction is a common lisp-ism. A "bignum" implementation in the
context of this article won't allocate for the small values.

------
catnaroek
Wrapping arithmetic makes perfect sense in many ways:

(0) It is still a commutative ring, so it has the most important nice
algebraic properties of number arithmetic: addition and multiplication are
associative and commutative, multiplication distributes over addition, etc.

(1) It is the ideal building block for implementing the very big integers that
high-level languages need.

Trapping on overflow is a relatively minor concession in elegance: You are
admitting that you want all integers, but, for efficiency reasons, constrain
yourself to those that fit into some fixed amount of space. The worst that can
happen is that you can't compute the result you want. You never compute a
nonsensical result.

Saturating arithmetic is an abomination and simply shouldn't exist.

~~~
grandinj
Saturating math is mainly a DSP thing, where it can be very useful in
mimicking the behavior of some analog circuits. And it makes some algorithms
have much nicer boundary behavior.

~~~
catnaroek
I confess my ignorance. Maybe in a DSP context saturating arithmetic makes
sense.

------
js8
I have to ask again: Why isn't there a better hardware support for overflows
on x86? Mainframe CPUs do have an option to raise an exception condition on
arithmetic overflow.

~~~
hyperpape
I can't comment on his assessment, but here is what Dan Luu says:
[http://danluu.com/integer-overflow/#fnref:H](http://danluu.com/integer-
overflow/#fnref:H)

~~~
jerf
Based on discussions I've had here, I do agree that step one is just for more
people to care. If more people care, it will eventually get the attention of
hardware people.

I mostly care about this issue because of the security issues that arise
(overflow is somewhere around #4 or #5 on the list of top root causes of
security issues), and for all that a lot of programmers _think_ they care
about security, there's still a lot of programmers that are quite cavalier
about fixing the root causes of these issues rather than occasionally fixing
symptoms when you think of it.

~~~
pcwalton
> I mostly care about this issue because of the security issues that arise
> (overflow is somewhere around #4 or #5 on the list of top root causes of
> security issues)

How many of those overflow issues crucially have to be combined with memory
safety problems to be weaponizable? I suspect the ratio is over 95%.

~~~
jerf
I do also care about the correctness issues. It's hard to write correct
software on top of a foundation that will happily tell you that 130 + 130 = 4.
There's a fundamental mismatch between what people think the operation means,
and what it really means, and that's never a good thing.

------
justinlardinois
> [Bignum] isn’t yet implemented widely enough, but happily there are plenty
> of languages such as Python that give bignums by default.

This was news to me, and when I looked it up I found out why: that's only the
case in Python 3. Python 2 has a type called int that's implemented as a C
long and a type called long that's a bignum [0]; Python 3 just has the latter
and it's called int [1].

[0] [https://docs.python.org/2/library/stdtypes.html#numeric-
type...](https://docs.python.org/2/library/stdtypes.html#numeric-types-int-
float-long-complex)

[1] [https://docs.python.org/3/library/stdtypes.html#numeric-
type...](https://docs.python.org/3/library/stdtypes.html#numeric-types-int-
float-complex)

~~~
Dylan16807
They both give you bignums by default. They only differ slightly in
implementation.

    
    
      Python 2.7.6 (default, Jun 22 2015, 17:58:13)
      [GCC 4.8.2] on linux2
      Type "help", "copyright", "credits" or "license" for more information.
      >>> type(10)
      <type 'int'>
      >>> type(10 ** 100)
      <type 'long'>

~~~
justinlardinois
Well, not quite. The documentation summarizes what your experiment showed.

> Unadorned integer literals (including binary, hex, and octal numbers) yield
> plain integers unless the value they denote is too large to be represented
> as a plain integer, in which case they yield a long integer.

~~~
Dylan16807
That's not a literal. The result of an expression that overflows int is a
long. 'int' is merely an efficient way of storing small integers as part of
the larger bignum scheme.

    
    
      >>> z=2**62
      >>> type(z)
      <type 'int'>
      >>> type(z+z)
      <type 'long'>

~~~
justinlardinois
Point taken. So if I'm understanding right, Python 2 converts int to long when
necessary, whereas in Python 3 int is already a long to begin with.

~~~
Dylan16807
The distinction is probably still there but slightly more hidden from view.
It's a good optimization, no reason to throw it out.

It would take some prodding around to find out.

~~~
__s
All python ints/longs are boxed (there's a cache for like -5 to 256). The
interpreter often checks if the digit count is 1, in which case it can fall
into basic integer addition of C. This is an implementation detail tho; if I
do type(x) I shouldn't have to check for both int & long. So they store that
information as part of the object type, rather than in the type of the object

In the past some have implemented tagged integers for Python, good
benchmarking results but the devs argue 'Good Python code should be more
symbolic; it shouldn't be doing too much integer math' which also comes up
where the interpreter doesn't implement specialized code for floats, instead
they argue that if you need fast floating point you should be using numpy or a
C extension

Tagged ints: [https://mail.python.org/pipermail/python-
dev/2004-July/04613...](https://mail.python.org/pipermail/python-
dev/2004-July/046139.html)

------
ramshorns
The article mentions that undefined behavior permits wrapping and trapping
implementations. What about bignums? Would a C or C++ implementation be
allowed to allocate more space to deal with integer overflow?

On the one hand, C data types are supposed to be fixed sizes, but on the
other, "integer overflow = undefined behavior = anything can happen" seems to
permit extending the size of the type when it overflows.

~~~
TorKlingberg
I may be late to answer here, but no you couldn't reasonably make int a bignum
in C. Unsigned integers are defined to wrap around, so certainly not there.

Signed overflow is undefined behaviour so the compiler and runtime can do
anything. But, where would it store the extra bits? It could call malloc, but
it wouldn't know when to free. It would violate the expectation that memory is
managed manually in C. Not all C code even uses malloc. Or, what if the int is
in a struct? Should the implementation go allocate extra overflow space
elsewhere, and then what happens when you memcpy the struct?

I think there is a strong impedance mismatch between bignums and languages
that aren't garbage collected.

------
douchescript
There's always the overflow flag in the cpu, I don't get why it's not readily
available in C when needed.

~~~
effie
Indeed, strange. There are also other flags for floating point exceptions, but
I have never heard about anyone actually using them. I'd like to have a look
at language that supports these flags - do you know any?

~~~
douchescript
The compilers have it, google __builtin_add_overflow as an example.

------
beached_whale
SafeInt also does wonders and stops you from doing things you shouldn't
without going to extremes, at least the last time I used it.
[https://safeint.codeplex.com/](https://safeint.codeplex.com/)

------
thewisenerd
Before going to solutions, people need to be made aware of it first.

Take any "introduction to programming" course, and it is likely to include a
question on: "swap two variables without using a third variable". When basic
algebra is the simplest way to solve it, you also need to point out of the
obvious case of integer overflow.

When I asked my professor why I wasn't graded for using the XOR-swap for
solving that question, his reply was "we do not expect students to know this".

------
runeks
Haskell's Integer type transforms an exception into a decrease in performance
by, behind the scenes, using the native (32/64 bit) integer type for values in
that range, and a custom bignum implementation for when the native type would
overflow.

------
zde
Uhm, why use integers when you have floats that don't overflow but saturate
nicely. lua/perl/js did just that.

~~~
ajuc
Floats cause even more problems.

------
mamcx
So, it will make sense to make Int64 the default even in 32 bits?

