4 ms·
> 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
by legulere 10y ago
> 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.
- kzrdude 10y ago> That's only true for signed integers, overflow is defined as wrapping for unsigned integers. Overflow is an error condition. Unsigned integer addition is defined to wrap around, and does not have this error. C11 standard says this like: > A computation involving unsigned operands can never overflow, > because a result that cannot be represented by the resulting unsigned integer type is > reduced modulo the number that is one greater than the largest value that can be > represented by the resulting type.
- couchand 10y agoThe article argues that wrapping by default is the wrong choice, and that C and C++ got it right with UB-on-overflow. GP points out that for unsigned integers, C and C++ got it wrong, too. The C11 standard agrees that when an integer would overflow, instead it wraps, while carefully avoiding saying that it's overflowing simply to preserve the defined term "Overflow" for the error code. In contexts other than the C11 standard it's fair to say that the integer overflowed, even if the language specifies that it wraps and "can never overflow".
- mdadm 10y ago>As an example compile this with optimisations (should work the same with signed integer overflow): Interesting. Clang and GCC both produce a binary that gets terminated by SIGFPE under normal compilation, but using -O3, Clang's output will run correctly while GCC's output will still SIGFPE.
- legulere 10y agoNot that surprising for the unoptimized binary, the expression simply doesn't get folded and the division is executed on the processor. x86 processors trap when they do a division by zero. This is a totally correct execution with C, as division by zero is undefined behaviour. You would probably even expect that to happen. I can't tell you though why the GCC binary even with optimisations outputs SIGFPE though, they probably don't optimize as much with undefined values.
- nkurz 10y agoAs 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 10y agoCheck 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 10y agoOK, 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.
- mzs 10y agoIt's worse than you might expect, try it with unsigned int ;) $ cc -O3 foo.c $ ./a.out 5e574b68 $ cat foo.c #include <stdio.h> int main(int argc, char ** argv) { unsigned int a = 1; unsigned int b = 0; printf("%x\n", a/b); } Yes overflow wraps on unsigned as defined, but that example is divide-by-zero which is UB for unsigned too and the compiler is free to optimize there, which LLVM does.