So, those are the buffer overflows.
Most people when they
hear the word overflow think of buffers.
Very few think of numbers,
and yet overflowing numbers can be
just as dangerous if not more so.
This slide shows the goals
of what we're going to talk about,
and also some issues that can cause numeric overflows.
The point of all this is when you read a number,
be sure it's within its range.
If you do that, these attacks go away.
So, the next slide goes into a little more detail about
what a what a numerical overflow is.
The program may assume that
certain numbers within a bound.
For example, not more than 256,
not more than 3,024,
not more than minus 32,235 and so forth.
So, the question is,
how do you know it's going to stay within that range?
And the answer usually is what we assume
it or the interfaces drive this.
So, what you then do is look for ways to
overflow or underflow these variables.
A proper program will check for this.
Most programs don't surprisingly enough,
and in the second bullet
where it says common error: ignore overflow.
Overflow, where n is the number of bits in the word,
and if it's greater than 2 to the n minus 1.
For example, it's the number of bits.
So, if n is eight,
then you have 15 possible.
So, what you would want to do is instead
of uploading something 0-15,
you want to upload something like 15-25 or whatever,
and so that way the number is too big,
and so it will either get truncated or discarded,
or it should get truncated or discarded.
And in C and C++ language types are very rich,
and when you do type punning,
going in this context,
going between signed and unsigned all sorts
of interesting things can happen.
So now, let me give you an example of
an overflow caused problems,
and this was back when most machines were 16 bits.
There was a system called the network file system,
NFS, and that was designed for diskless workstations.
So, you would go out to NFS,
and then pull down the boot file and boot.
It did a lot of other uses as well,
and it's quite popular within security community.
So, how did the NFS interface with the kernel?
Well, basically required a UID from
the client and file information to
determine whether or not the client
could in fact access that file.
NFS also used 32-bits, not 16-bits.
That was to avoid a well-known conflict.
All right. So, here's how it works.
Normally, the NFS server invokes
the kernel with the idea of a remote user,
and the kernel does the access control checking.
But wait, that means if I'm root
on my diskless workstation,
I can get root do anything root to do on server.
This was generally considered a bad idea.
So, they disallowed the root UID 0.
If the client tries to send over a
zero when it reaches the server,
the server will change it to a minus two,
which restricts greatly what it can read,
and doesn't allow it to go beyond that.
You can, by the way, override
this in a configuration file,
but it's fairly rare.
So now, the kernel is 16-bits
and UID is a 32-bits. There's a mismatch here.
So, what happens if I give NFS a UID
of more than 32-bits or more
than the function can handle?
Well, it turns out what happens is,
I get the string, sorry,
I get this UID, and
the client checks and says, "That's not zero."
He sends it over to the server.
The server checks, and says,
"That's not zero either."
But what I've done is I've crafted the number
so that the low 16-bits are all zero,
but of course when it does
the comparison it compares the whole number,
not just the low-order 16-bits.
That gets given to the kernel.
Kernel can't handle 32-bit UID.
Remember, it's a 16-bit machine.
So, it just lops off
the high-order 16-bits and throws them away.
Well, guess what that remaining portion is?
It's zeros, and so now I
have root access to the file system.
Moral of the story,
always check for overflows,
and in particular where you're
accepting data from another program.
Their needs may not match yours,
in which case you need to be upfront and tell them that,
and here's the details of the attack.
The 2 to the 17th NFS
does the comparison based on what's sent over,
which is 32-bit integer.
But the kernel tosses the high-order bits.
This requires understanding
the legal ranges of the integers,
and there are several ranges that are very, very common.
First of all, non-negative, positive or zero.
Second is when the numbers,
what we want the length of
the integer less than a certain number of bits,
and the reason it's
phrased this way is if the number is negative.
You clobber one particular bit
that's not been discussed yet.
You don't want that to happen,
and all you can say at most 1,000.
We did that when we talked about LPR in the last lesson.
The best thing to do is,
if I figure out how these numbers are interpreted,
and the source code is your best guide.
It may not be what you want,
but at least you'll know what's there.
You can use chasing functions to trace
the execution of a program,
especially as it interacts with the kernel,
and so in that case you can do tracing,
and also occasionally some documentation
is badly written enough so you
can infer where you stand.